Commit Graph

1046 Commits

Author SHA1 Message Date
schalli 5a03b75f1e refactor(quick-260921-jt4): ueberfluessige case-Marke in tender-normalizer.service.ts entfernt (Restposten 2)
Die Fallmarke 'doe-opendata' stand unmittelbar ueber default und fiel
in denselben Zweig — 260921-bi2 liess sie stehen, weil der Kommentar
darunter Absicht dokumentiert. Die Absicht laesst sich ohne die
Fallmarke ausdruecken und wird dabei deutlicher: der erweiterte
Kommentar traegt jetzt beide Aussagen (DÖE-Quelle landet hier UND
kuenftige additive SourceType-Mitglieder sollen ebenfalls hier landen
statt zu scheitern). Kein Verhaltenswechsel — derselbe Zweig wie
vorher. Belegt durch die vorhandenen 18 tender-normalizer-Tests.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 14:57:40 +02:00
schalli 69fe706580 fix(quick-260921-jt4): vier <img onError>-Avatarbilder tragen aria-hidden (D-03)
header.tsx, account-settings-form.tsx, favorites-widget.tsx (zweimal):
alle vier tragen bereits alt="", sind also schon aus dem
Zugaenglichkeitsbaum genommen; aria-hidden sagt dasselbe nur
ausdruecklich. Ehrliche Einordnung: richtige Auszeichnung, 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.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 14:57:01 +02:00
schalli f7b5df4db8 fix(quick-260921-jt4): zwei Schaltflaechenreihen tragen role="toolbar" (D-03)
calculator-widget.tsx Speicherzeile und favorites-widget.tsx
Ansichtsumschalter waren schlichte <div> mit aria-label, das die Rolle
generic stillschweigend verwarf. role="toolbar" ergaenzt (geprueft
sauber; role="group"/"region" loesen useSemanticElements neu aus).

Taschenrechner: fest verdrahteter Text wandert in
calculator.memoryLabel; bei dieser Gelegenheit auch Anzeigefeld
(displayLabel) und Rueckschritt-Taste (backspaceLabel) in den Katalog
gezogen (D-05), da diese Datei ohnehin geaendert wird.

Favoriten: die bisherige Beschriftung ("Favoriten") war sachlich
falsch fuer einen Listen/Kachel-Umschalter — neuer, zutreffender
Schluessel favorites.viewModeLabel statt des wiederverwendeten
favorites.name.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 14:56:02 +02:00
schalli b406a9c5f7 fix(quick-260921-jt4): drei Statussymbole in calendar-settings-panel tragen role="img" (D-03)
Die drei <span> mit aria-label ("Sync error"/"Connection OK"/
"Connection error") hatten die Rolle generic, die keine ARIA-Merkmale
traegt — die Beschriftung wurde stillschweigend verworfen. role="img"
ergaenzt (geprueft sauber, keine useSemanticElements-Neuausloesung).
Die Beschriftung ist hier der einzige Text des Symbols (svg bereits
aria-hidden) — Ursache behoben, nicht das Attribut gestrichen.

Erfolgsfall nutzt den vorhandenen Schluessel calendar.connectionSuccess
woertlich; fuer die beiden Fehlerfaelle neue kurze Schluessel
syncErrorLabel/connectionFailedLabel (der vorhandene connectionError
ist ein ganzer Hinweissatz, als Symbolbeschriftung zu lang). Die drei
fest verdrahteten englischen Texte damit aus der deutschen Oberflaeche.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 14:54:06 +02:00
schalli 9aa87bd000 fix(quick-260921-jt4): autoFocus von vier Seitenformularen entfernt (D-02)
login/page.tsx, reset-password/page.tsx, reset-password/[token]/page.tsx,
change-password/page.tsx: alle vier sind Seitenformulare, kein Dialog —
D-02 verlangt hier das Entfernen, nicht den ref+Effekt-Ersatz.
Fokus-Klauen beim Seitenaufruf ist genau der Schaden, gegen den
noAutofocus existiert. autoComplete/required unveraendert.

Nebennutzen bei change-password: der Hinweisbereich zum erzwungenen
Passwortwechsel steht unmittelbar ueber dem Formular — bislang sprang
der Fokus daran vorbei, eine Vorlesehilfe las ihn nie vor.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 14:52:40 +02:00
schalli e651c24647 fix(quick-260921-jt4): Kalender-Tageszelle wird echte Schaltflaeche fuer Tage mit Terminen
- Zelle rendert als <button type="button"> NUR wenn hasEvents gilt,
  sonst unveraendert als <div> ohne Handler (D-01) — 42 neue Tab-Stopps
  waeren eine Verschlechterung
- onFocus/onBlur mit demselben Rumpf wie onMouseEnter/onMouseLeave,
  damit die Termin-Einblendung auch per Tastatur erscheint/verschwindet
- cellClass unveraendert uebernommen, nur w-full text-left ergaenzt
  (D-04, kein optischer Unterschied); widgetNoDrag bleibt erhalten
- aria-label nennt Datum und Terminzahl ueber neue Katalogschluessel
  widgets.calendar.dayEventsOne/dayEventsMany (Mehrzahl-Konvention wie
  configMaxEventsOne/Many, D-05)
- Neue Tests: Tag mit Terminen ist <button> und reagiert auf
  Fokus/Weggehen wie auf Maus-Hover; Tag ohne Termine bleibt <div>;
  Einzahl-/Mehrzahl-Beschriftung; echter Tab-Stopp nachgewiesen

Nach diesem Umbau: klick-regeln 5 (nur die vier <img onError> +
Taschenrechner-Rahmen), semantic 0, a11y gesamt 14, errors 0 — Aufgabe
1 des Plans vollstaendig.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 14:51:41 +02:00
schalli b601141bcf fix(quick-260921-jt4): Notiz-Aufgabenkaestchen bedient sich selbst
- NoteCheckbox traegt jetzt ein echtes onChange und gibt sein eigenes
  DOM-Element an onToggle weiter; readOnly entfaellt (D-01) — war bisher
  nur da, um Reacts Warnung ueber ein gesteuertes Feld ohne onChange zu
  unterdruecken
- Index-Ermittlung bleibt wortgleich (alle Kaestchen im Behaelter
  einsammeln, indexOf auf dem ausloesenden Element), wandert aber vom
  Behaelter-onClick in handleCheckboxToggle, das den Behaelter ueber ein
  ref statt event.currentTarget findet
- previewOptions als useMemo mit leerer Abhaengigkeitsliste, Rueckruf
  ueber ein ref erreicht — identitaetsstabil wie die alte Modulkonstante,
  T-JT4-03: rehypePlugins: [[rehypeSanitize]] unveraendert erhalten
- Tests: echte Tastaturbetaetigung (Leertaste auf fokussiertem
  Kaestchen) UND echter Klick loesen onToggle/PATCH aus; neue Tests
  belegen readOnly/disabled entfallen

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 14:47:45 +02:00
schalli 0c89c13bb2 fix(quick-260921-jt4): calendar-settings-panel Loeschdialog-Hintergrund wird echte Schaltflaeche
- Zentrierbehaelter verliert den e.target===e.currentTarget-Handler,
  die Hintergrundfarbe wandert auf eine eigene benannte <button> (D-01)
- Muss abbrechen, darf niemals loeschen (T-JT4-04): Beschriftung nennt
  ausdruecklich das Abbrechen (widgets.calendar.deleteDialogCancel)
- Dialogkarte bekommt relative, aria-label des alertdialog aus dem
  Katalog statt fest verdrahtetem Englisch (deleteDialogLabel)
- Neue Testdatei belegt per Klick UND echter Tastaturbetaetigung, dass
  der Hintergrundweg abbricht und deleteSource nie aufgerufen wird;
  eigener Test fuer den tatsaechlichen Loeschweg ueber die CTA

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 14:43:36 +02:00
schalli 3d0bc0bfaa fix(quick-260921-jt4): widget-catalog-modal Hintergrund wird echte Schaltflaeche
- Aeussere Flaeche verliert onClick, die bislang nur optische
  Hintergrund-Flaeche wird eine benannte <button> und traegt onClose
  (D-01); verliert dabei aria-hidden, weil ein fokussierbares Element
  nicht verborgen sein darf
- stopPropagation auf der Dialogflaeche entfaellt als toter Code, weil
  der Hintergrund jetzt Geschwister statt Vorfahr ist
- Fest verdrahtetes englisches aria-label="Close" durch common.close
  ersetzt
- Neuer Katalogschluessel widgets.catalogClose in de.json/en.json
- Neue Testdatei: Hintergrund schliesst (Klick + Tastatur), Dialogklick
  schliesst nicht, Escape weiterhin, Kartenauswahl fuegt Widget hinzu

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 14:41:54 +02:00
schalli a8531d44df fix(quick-260921-jt4): MarketplaceCard-Klick wird echte deckende Schaltflaeche
- Karten-onClick entfernt, deckende <button> als Geschwister traegt
  handleCardClick nur wenn isActive (D-01, verschachtelte Buttons
  vermieden wie bei DropZone.tsx aus 260921-bi2)
- Aktivieren/Deaktivieren-Schaltflaeche bleibt eigener Tab-Stopp,
  Fussbereich bekommt relative fuer korrekten Stapelkontext
- Neuer Katalogschluessel marketplace.openDetail in de.json/en.json
- Tests: echte Tastaturbetaetigung (Enter auf fokussierter
  Schaltflaeche) statt Klick-Behauptung; kein Overlay im
  nicht-aktivierten Zustand

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 14:40:18 +02:00
schalli 7557c9aafe docs(quick-260921-jt4): Plan fuer 30 a11y-Befunde und vier Restposten
29 der 30 Fundstellen werden behoben, eine bleibt begruendet stehen.
Loesungswege vorab empirisch an Biome 2.5.0 geprueft: der in D-01 als
Rueckfall genannte Weg (role+tabIndex) taeuscht -- er tauscht drei Befunde
gegen einen neuen useSemanticElements-Befund und wird deshalb nirgends
benutzt.

Zwei Annahmen der Auftragsbeschreibung beim Nachlesen korrigiert:
vier der elf Klick-Befunde sind onError-Handler an img-Elementen, und
alle fuenf ARIA-Befunde sind aria-label auf rollenlosen Elementen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 14:35:29 +02:00
schalli 6cc0d02e97 docs(quick-260921-iwr): 30 Stellen geprueft, kein echter Fehler darunter
Tessera CI/CD / Lint & Type Check (push) Successful in 53s
Tessera CI/CD / Tests (push) Failing after 11m29s
Tessera CI/CD / Desktop-Pakete bauen (push) Has been skipped
Tessera CI/CD / Build & Publish Images (push) Has been skipped
Zusammenfassung und STATE.md zum Quick-Vorgang 260921-iwr. Damit ist
der fehlerverdaechtige Lint-Rueckstand vollstaendig geprueft.

Beide Verdachtsmomente widerlegt, durch Messung statt Argument: die
LDAP-Seite nutzt fuer die Liste, die wirklich waechst und schrumpft,
laengst eine stabile Kennung; und im Cert-Manager enden alle vier Pfade
mit einer 83-Byte-Schrottdatei bei 400, nie 500 - node-forge wirft,
statt still ein falsches Zertifikat zu bauen.

Ein Fund dreht die Richtung um: bei grants/page.tsx waere die
Korrektur schaedlich, dort ist die Positionsnummer fuer die
Eindeutigkeit noetig.

Geaendert: 5 Stellen. 25 bleiben bewusst stehen und bleiben in der
Zaehlung sichtbar, mit Begruendung je Stelle in der Akte.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 14:10:51 +02:00
schalli de6986340d fix(quick-260921-iwr): mergeCerts-Waechter loest keinen zusaetzlichen Lint-Fund mehr aus
Die urspruengliche findIndex((c) => c === null)-Pruefung erzeugte einen
neuen lint/complexity/useIndexOf-Fund (info) und hob TOTAL dadurch auf 430
statt der erwarteten 429 an — die Endverifikation des Plans deckte das auf
(D-06: TOTAL darf nirgends anders steigen).

@types/node-forge deklariert Bag.cert als "Certificate | undefined", die
node-forge-Laufzeit setzt bei einem unlesbaren Bag aber "null" (nicht
undefined). Ein blosses indexOf(null) ist deshalb nicht typsicher; die
Pruefung testet jetzt ausdruecklich auf beide Werte, was fuer biome kein
Single-Value-Vergleich mehr ist und keinen useIndexOf-Vorschlag ausloest.

TOTAL steht jetzt bei 429 wie geplant, NONNULL unveraendert bei 6, tsc und
beide Testsuiten weiterhin gruen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 14:05:13 +02:00
schalli 27909e4502 refactor(quick-260921-iwr): zwei ueberfluessige Ausrufezeichen in imap.provider.ts entfernt
imapflow typisiert uid in FetchMessageObject als Pflichtfeld (lib/imap-flow.d.ts
Zeile 469, Kommentar "Always included in the response"). Die beiden
Zusicherungen msg.uid! sicherten also einen Wert ab, der ohnehin nicht fehlen
kann. Reine Lesbarkeitsaenderung ohne Verhaltensaenderung, tsc bleibt gruen.

Die drei verbliebenen Zusicherungen (tenders.controller.ts:244,
favorites-widget.tsx:149, sidebar.tsx:80) bleiben unveraendert — jede durch
eine konkrete vorgelagerte Zeile garantiert (Provider-Eintrag, gemeinsame
Herleitung aus favorites, Anlegen des Map-Eintrags direkt davor).

NONNULL 8 -> 6, keines davon mehr in imap.provider.ts.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 14:03:01 +02:00
schalli b4aaed4b7c fix(quick-260921-iwr): drei bag.cert-Zusicherungen in cert-manager.service.ts zu echten Waechtern gemacht
node-forge setzt bag.cert bei einem wohlgeformten, aber nicht als X.509
lesbaren Zertifikats-Bag auf null (lib/pkcs12.js Zeile 703-709). Die drei
Zusicherungen in parseCert, mergeCerts und convertCert behaupteten also
etwas Falsches, obwohl die Folge bereits behandelt war: alle vier Pfade
antworteten schon vorher mit 400, nie mit 500 (mit einer selbst gebauten
83-Byte-PFX nachgemessen).

Ersetzt die drei Zusicherungen durch ausdrueckliche Pruefungen mit
praeziser BadRequestException. In mergeCerts wird kein Zertifikat mehr
verschluckt: alle Bags werden auf Vollstaendigkeit geprueft, bevor die
Liste ueber ein Typpraedikat zurueckgegeben wird.

RED-Tests zuerst geschrieben und mit den heutigen Meldungen ("Failed to
extract certificate details" / "Failed to create merged certificate
output" / "Failed to convert certificate to pem: serialization error")
rot bestaetigt, dann die Waechter ergaenzt: GREEN.

Verhaltensaenderung ausdruecklich beabsichtigt (D-02): nur der Text der
Fehlermeldung fuer diese eine Eingabeklasse aendert sich, der Statuscode
bleibt in allen vier Pfaden 400.

NONNULL 11 -> 8, davon 3 in cert-manager.service.ts (Zeilen 133/516/661
bleiben unveraendert — durch Hash-Laenge bzw. vorgelagerte Passwort-
Pruefung garantiert).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 14:01:36 +02:00
schalli 8716fa5234 feat(quick-260921-iwr): Rundenliste der Stoppuhr per Test belegt, MergeTab-Entfernen abgesichert
- Wirkungslose eslint-disable-Zeile in stopwatch-widget.tsx ersetzt durch
  Sachhinweis: Zeilen halten keinen Zustand, Rundennummer wird aus Laenge
  und Position berechnet. Keine neue Unterdrueckung, ARRAYKEY bleibt bei 19.
- Neuer Testfall in stopwatch-widget.test.tsx: zwei Runden nacheinander,
  neuere Runde steht oben, Rundennummern 2/1 stimmen zu ihrer eigenen Zeit.
- Neue MergeTab.test.tsx: Entfernen der mittleren Datei laesst genau erste
  und dritte Datei mit eigenem Namen und eigenem Entfernen-Knopf uebrig.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 13:55:42 +02:00
schalli cfba3c9532 docs(quick-260921-i8x): fuenf Fehlerklassen geprueft, kein echter Fehler darunter
Tessera CI/CD / Lint & Type Check (push) Successful in 54s
Tessera CI/CD / Tests (push) Successful in 1m35s
Tessera CI/CD / Desktop-Pakete bauen (push) Successful in 19s
Tessera CI/CD / Build & Publish Images (push) Successful in 3m10s
Zusammenfassung und STATE.md zum Quick-Vorgang 260921-i8x.

Zwoelf Stellen einzeln beurteilt: sieben gleichwertig oder Absicht,
zwei Haertungen, drei idiomatisch korrekt. Kein echter Defekt.

Die eine Stelle mit echtem Wert ist safe-next.ts, der Schutz gegen
Weiterleitung auf fremde Seiten. Der Kommentar behauptete Escapes,
die rohen Bytes zeigten eingebettete Steuerzeichen. Jetzt echte
Escapes; Gleichwertigkeit ueber alle 65536 Codepunkte nachgerechnet,
54 abgelehnte Zeichen, null Abweichung, vom Orchestrator unabhaengig
gegen ein eigenes Referenzmuster gegengeprueft.

Bewusst nicht angefasst: die NUL-Maskierung in ldap.service.ts nach
RFC 4515 - genau dieses Zeichen zu treffen ist ihr Zweck.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 13:36:37 +02:00
schalli b92dd5dda1 fix(api,web): exec-Schleifen ohne Unterdrueckungskommentar umgeschrieben, LDAP-NUL-Maskierung und Sprachcookie-SameSite dokumentiert/gehaertet
Drei while ((m = re.exec(t)) !== null)-Schleifen (dkv-parser.service.ts,
dkv-parser.validate.ts, icon-discovery.service.ts) sind die korrekte
Standardform fuer globale Regexe - kein verrutschtes "=". Umgeschrieben auf
eine verhaltensgleiche for-Schleife, die ohne noAssignInExpressions-
Unterdrueckung auskommt: Zuweisung wandert in Initialisierung und
Fortschaltung der for-Schleife, Bedingung prueft weiterhin auf null.
Abfolge der exec-Aufrufe, lastIndex-Fortschritt und Rumpfinhalte
unveraendert. Neue Spezifikation dkv-parser.service.spec.ts deckt
parseDkvText erstmals eigenstaendig ab (zwei Fahrzeugbloecke, Rechnungsnummer
und -datum aus einer gemockten pdf-parse-Attrappe) - das Rueckfall-Tor fuer
diesen Umbau. dkv-parser.validate.ts bleibt bei 27 Fahrzeugbloecken/66
Transaktionen gegen die reale invoice.pdf identisch.

LdapService.escapeLdapFilterValue bleibt zeichengleich: der NUL-Treffer in
der Regel ist die von RFC 4515 vorgeschriebene \00-Maskierung, kein Fehler.
Ein biome-ignore-Kommentar dokumentiert das, statt die Funktion zu aendern.

locale-switcher.tsx setzt jetzt SameSite=Lax auf dem NEXT_LOCALE-Cookie -
path=/ und max-age waren bereits korrekt, es lag also kein Persistenzdefekt
vor. Ohne SameSite haengt die Uebertragung am Browservorgabewert statt an
einer Festlegung. Neue Spezifikation locale-switcher.test.tsx haelt die
vollstaendige geschriebene Cookie-Zeichenkette fest.

Biome-Warnungen 446 -> 434 (noControlCharactersInRegex/useIterableCallbackReturn/
noGlobalIsNan auf 0, noAssignInExpressions auf 4 und noDocumentCookie auf 17 -
beide Reste ausschliesslich in Testdateien, suppressions/unused auf 0).

Quick-Vorgang 260921-i8x, Task 3/3.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 13:32:31 +02:00
schalli 076ca4bf64 fix(web): Number.isNaN statt globalem isNaN in vier Date-Pruefungen, forEach-Rueckgabewert verworfen
Alle vier Stellen (TenderDetail, ResultsList, InvoiceHistoryTable) rufen
isNaN(d.getTime()) auf - Date.prototype.getTime() liefert immer number,
also findet an keiner der vier Stellen tatsaechlich eine Umwandlung statt.
Der Tausch auf Number.isNaN ist reine Haertung: sobald dort einmal ein
String ankaeme, wuerde globales isNaN ihn stillschweigend umwandeln statt
ihn als kaputte Eingabe zu erkennen. Kein Rueckfallwert geaendert.

In cert-manager/actions.ts gibt files.forEach jetzt keinen Wert mehr aus
der Schleifenfunktion heraus - forEach verwirft ihn ohnehin, die Aenderung
ist rein kosmetisch (useIterableCallbackReturn). Reihenfolge der
form.append-Aufrufe unveraendert.

Quick-Vorgang 260921-i8x, Task 2/3.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 13:28:07 +02:00
schalli f85c91be01 fix(web): Steuerzeichen in safe-next.ts als Unicode-Escapes statt Rohbytes
FORBIDDEN_CHARS_RE in sanitizeNextPath bestand aus drei rohen Steuerbytes
(NUL, US, DEL) statt Escapes - jeder Editor, Formatierer oder Minifier in
der Kette kann solche Bytes stillschweigend verschlucken. Umgeschrieben auf
Unicode-Escapes fuer den Bereich U+0000 bis U+001F und U+007F. Zeichenmenge
ueber alle 65536 Codepunkte aus U+0000 bis U+FFFF als unveraendert
nachgewiesen (54 abgewiesene Codepunkte, Bitmap-SHA-256
3d58108b87e4641e506602cc701a66d11ec19cf20551937fada1826f50ecbe64 vor und
nach dem Umbau identisch). Kommentar korrigiert: die vorherige Behauptung,
Escapes seien "am Edge" noetig, war falsch - zwischen den beiden
Escape-Schreibweisen gibt es zur Laufzeit keinen Unterschied.

Quick-Vorgang 260921-i8x, Task 1/3.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 13:26:25 +02:00
schalli 287799a6fc docs(quick-260921-gof): 21 Effekt-Abhaengigkeiten beurteilt, im Browser nachgemessen
Tessera CI/CD / Lint & Type Check (push) Successful in 52s
Tessera CI/CD / Tests (push) Successful in 1m9s
Tessera CI/CD / Desktop-Pakete bauen (push) Successful in 18s
Tessera CI/CD / Build & Publish Images (push) Successful in 3m50s
Zusammenfassung, Verifikation und STATE.md zum Quick-Vorgang 260921-gof.

Ergebnis der Einzelbeurteilung: nur 2 echte Defekte, 15 Fallen (das
naive Eintragen der Abhaengigkeit haette eine Abruf-Schleife erzeugt),
3 bewusste Ausnahmen, 1 Ballast. Die gefaehrlichste Stelle war
calendar-widget.tsx: showToday setzt bei jedem Klick ein frisches Date,
der naive Umbau haette jeden Druck auf den Monatstitel bis zum
Exchange-Server durchschlagen lassen.

Im Browser nachgemessen statt nur behauptet: Dashboard 62 s Ruhe ohne
zusaetzlichen Abruf, Monatstitel dreimal gedrueckt mit null zusaetzlichen
Abrufen nach dem ersten, Stoppuhr echtzeitgetreu ueber 6 s und ueber
4 Runden monoton, dazu acht weitere Ansichten je 20-25 s ruhen gelassen
mit genau einem Abruf je Endpunkt.

Warnungen 467 -> 446, useExhaustiveDependencies 0, web-Tests 66/462 ->
67/477, api unveraendert, type-check 4/4, pnpm lint 5/5.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 13:00:15 +02:00
schalli e780b2cc69 fix(web): Absicht/Ballast der letzten sechs useExhaustiveDependencies-Befunde entschieden
Befunde 16-21 (quick-260921-gof), Regel-Gesamtstand jetzt 0/446 (vorher
21/467), pnpm lint 5/5, pnpm type-check 4/4:

- InvoiceHistoryTable.tsx (16) und sidebar.tsx (17): refreshKey /
  sidebarRefreshKey bleiben als begruendete Auffrisch-Ausloeser stehen
  (biome-ignore mit deutschem Grund) - ohne sie zeigt die DKV-Historie
  nach "Jetzt pruefen" bzw. die Seitenleiste nach einer Modul-
  Aktivierung den alten Stand.
- ActivateModuleDialog.tsx (18): moduleId aus der Abhaengigkeitsliste
  entfernt - reiner Ballast, der Effekt holt ohnehin nur die vom Modul
  unabhaengige Gruppenliste und die Elternseite haengt den Dialog je
  Modul frisch ein.
- GroupMembersModal.tsx (19/20): fetchMembers/fetchAllUsers in die
  Liste aufgenommen (echter Defekt) - ohne sie zeigt der Dialog bei
  einem Gruppenwechsel ohne Neuaufbau die Mitglieder der vorigen
  Gruppe. Neue Testdatei nach dem grants-matrix.test.tsx-Muster belegt
  genau diesen Fall.
- grants/page.tsx (21): die Suchabgleich-Hilfsfunktion `matches` in den
  Merkungs-Rumpf verschoben statt im Bauteil-Rumpf zu bleiben - reiner
  Ballast, die bestehenden Suchfaelle bleiben unveraendert gruen.
- sidebar.test.tsx um eine Zaehlprobe erweitert: Bump loest genau einen
  weiteren Abruf aus, erneutes Zeichnen ohne Bump keinen.
- Alle drei begruendeten biome-ignore-Zeilen sind jetzt gesetzt (erste
  Verwendung dieses Mechanismus im Projekt), alle wirkungslosen
  eslint-disable-Zeilen fuer diese Regel sind aus apps/web/src
  verschwunden.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 12:34:55 +02:00
schalli e2c508cff5 fix(web): t-Falle in acht Ladeeffekten entschaerft, RssFeedListForm/SavedSearchBar-Ladefunktionen stabilisiert
Befunde 7-15 der Biome-Regel useExhaustiveDependencies (quick-260921-gof):

- VehicleTable, TenderDetail, DigestIntervalForm, SourceConfigForm,
  favorites-widget: Ersatz-Fehlertext aus useTranslations wird jetzt vor
  dem Effekt/Rueckruf in eine Konstante gezogen und diese Konstante in
  die Abhaengigkeitsliste aufgenommen - `t` selbst kommt nirgends mehr
  in eine Liste. In diesem Projekt ist belegt, dass `t` bei jedem
  Durchlauf eine frische Funktion ist (Testattrappen), eine `t`-
  Abhaengigkeit haette den jeweiligen Mount-Abruf zur Schleife gemacht.
- RssFeedListForm.tsx und SavedSearchBar.tsx: die Ladefunktionen waren
  gewoehnliche Funktionen im Rumpf (bei jedem Durchlauf neu) - jetzt in
  einen stabilen Rueckruf mit der Text-Konstante als einziger
  Abhaengigkeit eingepackt.
- ResultsList.tsx: Befund 8 (t) wie oben, Befund 15 (refreshKey) in den
  Effekt verschoben, der `load` aufruft, statt in `load` selbst zu
  stehen - eine begruendete `biome-ignore`-Zeile (erste im Projekt)
  haelt fest, dass der Auffrisch-Ausloeser der Elternseite ohne diese
  Abhaengigkeit wirkungslos waere.
- Sieben Testdateien um eine Zaehlprobe erweitert: erneutes Zeichnen mit
  unveraenderten Props darf keinen weiteren Abruf ausloesen; ResultsList
  zusaetzlich um eine Probe, dass ein refreshKey-Bump genau einen
  weiteren Abruf ausloest.
- DigestIntervalForm hat keine Testdatei - nur am laufenden System auf
  Meine Quellen geprueft (siehe SUMMARY).
- Wirkungslose eslint-disable-Zeilen fuer diese Regel entfallen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 12:29:05 +02:00
schalli b3f0e3cdcd fix(web): Kalender-Ladeeffekt an monthDate statt getTime(), Stoppuhr-Takt liest nur noch Einzelwerte
Befunde 1-6 der Biome-Regel useExhaustiveDependencies (quick-260921-gof):

- calendar-widget.tsx: useMemo um resolveCalendarConfig() entfernt
  (reine Funktion, spart nichts). showToday setzt monthDate jetzt
  identitaetserhaltend, wenn der aktuelle Monat schon angezeigt wird -
  erst danach durfte der Ladeeffekt von monthDate.getTime() auf
  monthDate umgestellt werden, sonst haette jeder Druck auf den
  Monatsknopf im laufenden Monat einen Termin-Abruf bis zum
  Exchange-Server ausgeloest (D-04).
- stopwatch-widget.tsx: neue reine Hilfsfunktion computeElapsedFrom()
  fuer den Takt-Effekt, der jetzt nur noch drei Einzelwerte statt des
  ganzen sw-Objekts liest - eine sw-Abhaengigkeit haette den 100-ms-Takt
  bei jeder aufgezeichneten Runde ab- und wiederaufgebaut.
- Testerweiterungen als Rueckfallsicherungen: 2x weiterblaettern -> 3
  Abrufe, 3x Monatsknopf im laufenden Monat -> kein Zusatzabruf; Runde
  waehrend die Stoppuhr laeuft unterbricht den Takt nicht, genau 1 PATCH
  je Klick.
- Wirkungslose eslint-disable-Zeilen fuer diese Regel entfallen (kein
  ESLint mehr im Projekt).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 12:23:35 +02:00
schalli 54fdf699dc docs(quick-260921-gof): Plan fuer die 21 Effekt-Abhaengigkeiten in React
Alle 21 Befunde der Regel useExhaustiveDependencies einzeln beurteilt und
in vier Klassen eingeteilt: 2x Defekt, 15x Falle, 3x Absicht, 1x Ballast.
Die Vorgabe kennt nur A/B/C -- Biome meldet aber auch ueberfluessige
Abhaengigkeiten, deshalb die vierte Klasse fuer reinen Ballast.

Acht Befunde sind dieselbe t-Falle aus dem Vorgang 260921-bi2: die
Testattrappen fuer next-intl liefern bei jedem Durchlauf eine frische
Funktion, t in eine Abhaengigkeitsliste einzutragen ist hier belegbar
eine Abruf-Schleife. Einheitlicher Griff: uebersetzten Text vor dem Hook
als Zeichenkette festhalten (Wertvergleich statt Identitaetsvergleich).

Kalender: showToday muss zuerst identitaetserhaltend werden, sonst loest
der naive Griff bei jedem Druck auf den Monatsknopf einen Termin-Abruf
bis zum Exchange-Server aus. Nachweis am laufenden System ueber das
Netzwerkprotokoll des Browsers, nicht per fetch aus der Seite.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 12:15:40 +02:00
schalli 525f212e39 docs(quick-260921-fi3): erzwungener Passwortwechsel an der API durchgesetzt
Tessera CI/CD / Lint & Type Check (push) Successful in 48s
Tessera CI/CD / Tests (push) Successful in 1m5s
Tessera CI/CD / Desktop-Pakete bauen (push) Successful in 18s
Tessera CI/CD / Build & Publish Images (push) Successful in 3m22s
Plan, Zusammenfassung, Verifikation und STATE.md zum Quick-Vorgang
260921-fi3.

Befund: auth.service legt mustChangePassword in den JWT, jwt.strategy
liess das Feld beim Auspacken fallen. request.user.mustChangePassword
war damit immer undefined und der global registrierte
ForcePasswordChangeInterceptor hat seit seiner Einfuehrung nie
blockiert. Durchgesetzt wurde der Zwangswechsel allein von der
Web-Middleware; jeder Weg daran vorbei umging ihn. Gemessen: eine
Sitzung mit mustChangePassword=true erhielt auf GET /users 200 samt
vollstaendiger Benutzerliste.

Behoben, und belegt bei gleicher Rolle und gleicher Route:
GET /modules/active liefert 403 FORCE_PASSWORD_CHANGE mit Zwang und
200 ohne. Neue Spezifikationen gegen den alten Stand 6 von 12 rot,
danach 12 von 12 gruen, vom Verifier unabhaengig nachgestellt.
Der Browser-Ablauf wurde vollstaendig durchgespielt: niemand wird
ausgesperrt, der Wechsel gelingt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 11:56:17 +02:00
schalli e56cce4bf3 fix(web): Doppelausloesung des Loeschknopfs sperren, Fahrzeugtabelle uebersetzt
VehicleTable.confirmDelete liess das Beschaeftigt-Kennzeichen zwar setzen,
aber nie lesen (const [, setIsDeleting]); Dialogschaltflaechen blieben
waehrend der laufenden Loeschanfrage bedienbar. Kennzeichen jetzt lesbar
gebunden, Dialog reicht den Zustand an Bestaetigen/Abbrechen weiter
(disabled + Sperr-Klassen), und confirmDelete bricht bei bereits laufender
Loeschung selbst ab.

Alle sieben fest verdrahteten Texte und sechs Vorlesehilfen der Tabelle
jetzt ueber next-intl (sieben neue Schluessel im Bereich dkvFleet, gleicher
Schluesselsatz in de.json und en.json).

Nebenbefund beim Testen: `load` haette mit `t` als Abhaengigkeit bei einem
instabilen Uebersetzer-Mock einen Abruf-bei-jedem-Render-Zyklus ausgeloest
— bewusst mit leerem Abhaengigkeitsfeld gelassen. Ausserdem
`vi.restoreAllMocks()` im Testabbau ersetzt: es leerte die Aufrufzaehlung
der reinen vi.fn()-Mocks nicht, wodurch der neue Doppelklick-Test falsche
Aufrufzahlen sah.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 11:39:03 +02:00
schalli f7c02b7fc9 fix(api): erzwungenen Passwortwechsel an der API wirklich durchsetzen
JwtStrategy.validate liess mustChangePassword auf dem Weg vom Token zu
request.user fallen; der global registrierte ForcePasswordChangeInterceptor
prueft genau dieses Feld und hat seit seiner Einfuehrung nie etwas
blockiert. validate() reicht das Feld jetzt durch (strenger Vergleich mit
true, Alt-Sitzungen ohne den Anspruch bleiben unveraendert unbetroffen).

Zusaetzlich die Erlaubnisliste des Abfangers von Teilstring-Vergleich auf
exakten Abgleich von Methode UND Pfad umgestellt (Absicherung gegen eine
kuenftige kollidierende Route, heute nicht ausnutzbar).

Nahttest gepinnt, der gegen den alten Quelltext nachweislich scheitert
(6 von 12 neuen Faellen rot vor der Aenderung, gruen danach).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 11:27:36 +02:00
schalli 116041b7fd docs(quick-260921-bi2): gemeldetes Symptom Passwortwechsel widerlegt
Tessera CI/CD / Lint & Type Check (push) Successful in 47s
Tessera CI/CD / Tests (push) Successful in 1m9s
Tessera CI/CD / Desktop-Pakete bauen (push) Successful in 18s
Tessera CI/CD / Build & Publish Images (push) Successful in 2m51s
Der Lint-Durchlauf meldete, die Seite change-password leite nach
erfolgreichem Wechsel nicht weiter und lasse die Person bei erzwungenem
Wechsel stehen. Am laufenden System durchgespielt: trifft nicht zu.
Middleware leitet auf /change-password, der Wechsel landet auf /,
mustChangePassword steht danach auf false, Weiternavigieren geht.

changePasswordAction setzt das neue Sitzungs-Cookie und ruft redirect('/')
serverseitig; die ungenutzten router/setUser im Seitenmodul waren
Ueberbleibsel, kein Symptom. Entfernung des toten Codes war richtig,
die daraus abgeleitete Diagnose nicht.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 11:05:58 +02:00
schalli f198c5765d docs: Stand nach CI-Lauf 389 (komplett gruen, Abbilder veroeffentlicht)
Tessera CI/CD / Lint & Type Check (push) Successful in 51s
Tessera CI/CD / Tests (push) Successful in 1m6s
Tessera CI/CD / Desktop-Pakete bauen (push) Successful in 17s
Tessera CI/CD / Build & Publish Images (push) Successful in 2m57s
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 10:15:23 +02:00
schalli c001a081c2 docs(quick-260921-bi2): Lint-Rueckstand 2923 -> 465, Akte und Verifikation
Tessera CI/CD / Lint & Type Check (push) Successful in 51s
Tessera CI/CD / Tests (push) Successful in 1m5s
Tessera CI/CD / Desktop-Pakete bauen (push) Successful in 5m24s
Tessera CI/CD / Build & Publish Images (push) Successful in 4m15s
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) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 10:02:44 +02:00
schalli 97a6836444 docs(quick-260921-bi2): Entwickleranleitung auf Endstand 465/386/79 gebracht
Der Lint-Abschnitt trug noch den Zwischenstand aus Aufgabe 1 (754/633/121).
Jetzt der tatsaechliche Endstand nach allen drei Aufgaben, beide Ausnahmen
weiterhin begruendet, plus die namentlich benannten Folgeaufgaben: fuenf
zurueckgestellte a11y-Regeln, die bewusst nicht angewendete
noUselessSwitchCase-Fundstelle, und die vier gemeldeten D-03-Symptomfunde.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 09:43:40 +02:00
schalli c79bafa179 fix(quick-260921-bi2): a11y - Beschriftungen an Felder binden (22 Fundstellen)
noLabelWithoutControl (22) auf 0: jede Beschriftung ueber htmlFor/id an ihr
Feld gebunden, in Formularen mit wiederholten Zeilen ueber praefixierte,
seitenweit eindeutige Kennungen (z.B. 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, darum nicht gewaehlt.

Damit steht der gesamte Lint-Rueckstand bei 465 (386 echt, 79 Test),
Fehlerstufe 0 -- Zielwert dieses Vorgangs erreicht. Die fuenf zurueck-
gestellten Regeln (noNoninteractiveElementInteractions, useKeyWithClick-
Events, noStaticElementInteractions, useAriaPropsSupportedByRole,
noAutofocus) stehen unveraendert bei 11/5/5/5/4.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 09:38:14 +02:00
schalli 21c85a8b88 fix(quick-260921-bi2): a11y - Rollen, Semantik, Tab-Reihenfolge (32er-Rest, Teil 1/2)
Vier der fuenf noch offenen Regeln aus Teillieferung B bereinigt:

- noRedundantRoles (4): ueberfluessige role-Angaben auf button/time/select
  entfernt (maschineller --unsafe-Fix, gelesen).
- useAriaPropsForRole (1): entfaellt automatisch mit obigem Fix -- das
  <select role="combobox"> in search-widget.tsx verlangte die fehlenden
  ARIA-Attribute nur wegen der ueberfluessigen Rolle.
- useSemanticElements (4): admin-sidebar/settings-sidebar tragen role=
  "navigation" jetzt am <nav> statt am <aside> (kein doppeltes Landmark
  mehr); widget-wrapper.tsx ist jetzt ein echtes <article> statt
  div role="article"; DropZone.tsx trennt die Datei-Entfernen-Schaltflaeche
  als Geschwister ab, damit die Drop-Flaeche selbst ein echtes <button>
  werden kann (ein <button> darf kein zweites <button> verschachteln).
  Die Drop-Flaeche traegt darum jetzt Klick- UND Drag-Handler direkt am
  <button>, sonst waere sie ein "statisches" Element mit Ereignis-Handlern
  geworden (die zurueckgestellten Regeln noStaticElementInteractions /
  noNoninteractiveElementInteractions waeren neu angeschlagen -- geprueft,
  bleiben bei 5/11).
- noNoninteractiveTabindex (1): calculator-widget.tsx traegt jetzt
  tabIndex={-1} statt {0}. Die Zifferntasten sind bereits echte <button>
  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.

Verbleibend: noLabelWithoutControl (22), naechster Schritt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 09:33:56 +02:00
schalli 73ac08af17 fix(quick-260921-bi2): a11y - Bildmarke und Desktop-Einrichtungsseite
apps/web/src/app/icon.svg (Next.js-Favicon, keine next-intl-Anbindung
moeglich): <title>Tessera</title> als einzige Fundstelle, die die ganze
Bedeutung allein traegt.

apps/desktop/src/setup.html: Bildmarke steht direkt vor der Ueberschrift
"Tessera" und wird dekorativ (aria-hidden="true").

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 09:21:49 +02:00
schalli 27a6e2952c fix(quick-260921-bi2): a11y - Layout-Navigation und Einstellungsformulare
useButtonType: Hamburger-, Profil-, Kategorie-, Ein-/Ausklapp- und Logout-
Buttons (kein Formular in diesen Dateien) erhalten type="button".

noSvgWithoutTitle: Icons neben sichtbarem Text werden dekorativ
(aria-hidden="true"). Zwei bislang unbenannte interaktive Elemente erhalten
zusaetzlich ein aria-label, weil ihr Text im eingeklappten Sidebar-Zustand
verschwindet bzw. ganz fehlte: die Dashboard-/Marketplace-Links und der
"Einstellungen"-Button in der Seitenleiste (t('dashboard')/t('marketplace')/
t('settings'), alle bereits vorhandene Schluessel) sowie der mobile
Schliessen-Button (tCommon('close')).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 09:21:43 +02:00
schalli 969fd01f5c fix(quick-260921-bi2): a11y - Dashboard-Widgets und Portal-Startseite
useButtonType: "Widget hinzufuegen"-, Bearbeiten-Modus- und Katalog-Buttons
(kein Formular in diesen Dateien) erhalten type="button".

noSvgWithoutTitle: Widget-Icons in der Katalog-Kachel (immer neben dem
Widget-Namen) und Buttons mit bestehendem aria-label/title werden dekorativ
(aria-hidden="true"); der bislang unbeschriftete Such-Button im Such-Widget
erhaelt aria-label={t('search.searchButton')} (neuer Schluessel, siehe
vorherige i18n-Festschreibung).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 09:21:34 +02:00
schalli 278aedb201 fix(quick-260921-bi2): a11y - Modul-Bereich (Kategorie-Seiten, Cert-Manager)
useButtonType: alle Aktions-Buttons in Cert-Manager-Tabs (kein Formular in
diesen Dateien) erhalten type="button".

noSvgWithoutTitle: Modul-Icons neben dem Modulnamen, Leer- und Nicht-
gefunden-Zustaende neben ihrer Ueberschrift werden dekorativ
(aria-hidden="true"); die Augen-Icons im Passwortfeld sind bereits ueber das
aria-label des umschliessenden Buttons benannt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 09:21:24 +02:00
schalli ae821254ef fix(quick-260921-bi2): a11y - Marketplace-Bereich
useButtonType: alle Filter-, Karten- und Dialog-Schaltflaechen (keine davon
in einem Formular) erhalten type="button".

noSvgWithoutTitle: Modul-Icons neben dem Modulnamen und die Erfolgs-/Fehler-
Symbole neben der Toast-Nachricht werden dekorativ (aria-hidden="true"); der
bislang unbeschriftete Toast-Schliessen-Button erhaelt aria-label={t('close')}
aus dem bereits vorhandenen common.close-Schluessel, sein Icon wird dekorativ.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 09:21:17 +02:00
schalli 4cff3167ed fix(quick-260921-bi2): a11y - Administrationsbereich (Gruppen, LDAP, Module, Mandanten, Benutzer)
useButtonType: alle Schaltflaechen ausserhalb der drei echten Formulare
(Mandanten/Benutzer/LDAP) erhalten type="button"; die drei tatsaechlichen
Absende-Buttons behalten type="submit" (Zahl bleibt 1/1/2, siehe <verify>).

noSvgWithoutTitle: Symbole neben sichtbarem Text (Zurueck-Pfeil, Navigations-
Icons in der Admin-Seitenleiste) werden dekorativ (aria-hidden="true"); das
Schloss-Symbol der LDAP-Standardzuordnung traegt jetzt einen eigenen Titel
(admin.ldap.fieldMapping.defaultIcon), weil es ohne begleitenden Text pro
Tabellenzeile die ganze Bedeutung allein traegt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 09:21:09 +02:00
schalli e76f3b8d84 fix(quick-260921-bi2): a11y - Login, Passwort-Reset und Passwort-Aenderung
useButtonType (Absenden-Button behaelt type="submit", keine weiteren Buttons
in diesen Dateien) und noSvgWithoutTitle (Lade-Spinner im Absenden-Button ist
rein dekorativ, da er den sichtbaren Beschriftungstext waehrend des Ladens
ersetzt -> aria-hidden="true").

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 09:20:59 +02:00
schalli 38115783b4 i18n(quick-260921-bi2): neue Schluessel fuer a11y-Beschriftungen (LDAP-Standardzuordnung, Suchbutton)
- admin.ldap.fieldMapping.defaultIcon: Beschriftung fuer das Schloss-Symbol der
  Standardzuordnung (Aufgabe 3, noSvgWithoutTitle)
- widgets.search.searchButton: Beschriftung fuer den bislang unbenannten
  Such-Icon-Button (Aufgabe 3, noSvgWithoutTitle)
- de/en Schluesselmengen bleiben identisch

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 09:20:53 +02:00
schalli 636fe0df8f refactor(quick-260921-bi2): maschinelle Lint-Fixe und toten Code abbauen
- Aufgabe 2: vier sichere Biome-Regeln (useImportType pfadgebunden auf
  apps/web+packages, noUselessEscapeInRegex, useConst,
  useExponentiationOperator) sowie fuenf ungesicherte Regeln
  (useNodejsImportProtocol, useLiteralKeys, useOptionalChain, useTemplate,
  useParseIntRadix) angewendet und den gesamten Diff von Hand gelesen
  (ldap.service.ts zeichenweise gegen Gross-/Kleinschreibung der
  AD-Merkmale, auth.service.ts/jwt.strategy.ts gegen Durchwinken bei
  fehlender Sitzung geprueft)
- noUselessSwitchCase bleibt bewusst stehen (tender-normalizer.service.ts:60,
  die Fallmarke dokumentiert Absicht)
- Toter Code (D-03): fuenf folgenlose Auffangvariablen entfernt, eine
  nicht benutzte Funktion (forSystemQuery, Pruefskript) entfernt, ein
  positionsgebundener Dekoratorparameter umbenannt (current-user.decorator.ts),
  fuenf Symptomfunde entfernt und als Folgeaufgaben zu melden (siehe unten)
- Sechs weitere, im Plan nicht namentlich gelistete aber
  gleich-kategorische Dead-Code-Fundstellen in Testdateien zusaetzlich
  bereinigt (groups.service.spec.ts, cert-manager.test.tsx,
  ldap.service.spec.ts, prisma-tenant.extension.spec.ts x3) — noetig, um
  die vom Plan selbst verlangten Nullstaende bei noUnusedVariables/
  noUnusedImports/noUnusedFunctionParameters zu erreichen

Dekoratordaten aus apps/api unveraendert (593 Zeilen, sha256 6e1583f1...).
Endstand 620 Befunde (541 echt, 79 Test) statt der im Plan geschaetzten
621/542 — eine Differenz von 1, weil das Streichen des Namens aus
`catch (e: any)` in calendar.service.ts (Symptom-Fix) den dort ebenfalls
gemeldeten noExplicitAny-Befund miteliminiert; das ist eine erwuenschte
Nebenwirkung, keine Regression. Fehlerstufe 0, beide Testlaeufe
punktgleich gruen (69/1124, 66/459), pnpm type-check 4/4, pnpm lint
--force 5/5.

Folgeaufgaben aus D-03 (nicht in diesem Vorgang behoben):
- force-password-change.interceptor.ts: Freigabeliste prueft nur den Pfad,
  nicht die HTTP-Methode
- change-password/page.tsx: nach erzwungenem Wechsel bleibt die Person auf
  der Seite stehen (keine Weiterleitung, keine Aktualisierung der
  Benutzerablage)
- VehicleTable.tsx: Loeschschaltflaeche hat keinen Besetztzustand, laesst
  sich doppelt ausloesen
- SplitTab.tsx: downloadAllAsZip erhielt eine ungenutzte
  Uebersetzungsfunktion, Hinweis auf fest verdrahtete Texte im Zip-Pfad

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 08:58:32 +02:00
schalli 8d1c8f320b chore(quick-260921-bi2): Lint-Konfiguration bereinigen, Messgrundlage herstellen
- overrides fuer Testdateien (noExplicitAny off) und apps/api (useImportType off, siehe
  Regelbeschreibung "Caveat with TypeScript experimental decorators")
- Fixture-Ausnahme in files.includes ohne angehaengten Doppelstern (Biome-Hinweis
  useBiomeIgnoreFolder befolgt)
- Entwickleranleitung auf den neuen Stand gebracht: 754 Befunde (633 echt, 121 Test), beide
  Ausnahmen begruendet, Folgeaufgaben benannt

Rueckstand: 2923 -> 754, Fehlerstufe weiterhin 0. security-Regelgruppe unangetastet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 08:46:06 +02:00
schalli e7c2c4c6a9 docs(quick-260921-bi2): Plan fuer den Lint-Rueckstand (2923 -> 466)
Drei Durchgaenge nach Risikoklasse: Konfiguration, maschinelle Fixes
plus toter Code, Barrierefreiheit von Hand. Alle Zielzahlen sind beim
Planen probeweise ausgefuehrt, gemessen und zurueckgenommen worden.

Wichtigster Befund: style/useImportType darf in apps/api nicht
maschinell laufen. emitDecoratorMetadata plus NestJS bedeutet, dass die
Korrektur 61 von 65 Dateien mit Abhaengigkeitsdaten zerstoert — bei
gruenem tsc und gruenen 1124 Tests, weil kein Test den Container
startet. Der Plan begrenzt die Regel auf apps/web und verankert als
Nachweis einen sha256 ueber die erzeugten __metadata-Zeilen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 08:39:32 +02:00
schalli 6de5eb4f07 docs(quick-260921-a1d): Benutzerverwaltung meldet abgewiesene Aktionen (WINDOWS #36)
Tessera CI/CD / Lint & Type Check (push) Successful in 55s
Tessera CI/CD / Tests (push) Successful in 1m11s
Tessera CI/CD / Desktop-Pakete bauen (push) Failing after 11m56s
Tessera CI/CD / Build & Publish Images (push) Has been skipped
Zusammenfassung und Verifikation zum Quick-Vorgang 260921-a1d,
Registereintrag #36 geschlossen, STATE.md nachgezogen.

Nachgewiesen: alle drei zuvor stillen Stellen (Liste laden, Formular
speichern, Loeschen) zeigen den Servertext oder eine uebersetzte
Ersatzmeldung; fuer einen ADMIN entfallen Bearbeiten und Loeschen in der
SUPER_ADMIN-Zeile. apps/api blieb unangetastet — der Zielrollen-Riegel
im Controller bleibt die wirksame Grenze. Web-Tests 66 Dateien / 459
Tests gruen (vorher 65/447), type-check Exit 0, pnpm lint 5/5.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 07:41:19 +02:00
schalli 13b70dfbe8 fix(web): SUPER_ADMIN-Zeile bietet einem ADMIN keine Aktionsknoepfe mehr an
WINDOWS #36, Aufgabe 3/3: canManageRow spiegelt den Zielrollen-Riegel aus
apps/api/src/user/user.controller.ts (update/remove, WINDOWS #29) rein
ergonomisch — die Serverpruefung bleibt unveraendert und ist die einzige
wirksame Grenze. Bearbeiten und Loeschen entfallen jetzt in der Zeile
eines SUPER_ADMIN, wenn die angemeldete Person selbst keiner ist;
Details bleibt in jeder Zeile. Die Sperre gegen Selbstloeschung bleibt
unveraendert. Gesamtbestand apps/web: 66 Dateien / 459 Tests gruen,
type-check Exit 0, lint 5/5 erfolgreich, apps/api unangetastet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 07:33:48 +02:00
schalli 51bff7564f fix(web): Formularweg und Listenladen der Benutzerverwaltung melden Fehler sichtbar
WINDOWS #36, Aufgabe 2/3: handleSubmit und fetchUsers verschluckten
abgewiesene Antworten und Verbindungsfehler ebenso wie der Loeschweg aus
Aufgabe 1. formError zeigt jetzt den Servertext oder eine Ersatzmeldung
im offenen Formulardialog; loadError verhindert die irrefuehrende
Meldung "Keine Benutzer gefunden", wenn das Laden selbst gescheitert
ist. Beide Zustaende werden beim Oeffnen eines neuen Dialogs
zurueckgesetzt, damit eine alte Meldung nicht in den naechsten Aufruf
hinueberwandert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 07:31:47 +02:00
schalli 38d2586466 fix(web): Loeschweg der Benutzerverwaltung meldet abgewiesene Server-Antworten
WINDOWS #36, Aufgabe 1/3: handleDelete verschluckte einen 403 bisher
komplett (nur res.ok geprueft, Fang-Zweig ohne Wirkung). readApiMessage
liest jetzt gezielt das Feld message aus dem Antwortrumpf; der
Loeschdialog zeigt den Servertext, eine uebersetzte Ersatzmeldung ohne
verwertbaren Rumpf oder bei Verbindungsfehler — und bleibt in allen drei
Faellen offen. Neue Texte unter admin.users.errors in de.json/en.json,
Umlaut-Waechter gruen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 07:29:51 +02:00
schalli 24f51e932d docs(quick-260921-a1d): Plan fuer WINDOWS #36 (stille 403-Antworten)
Drei Aufgaben: Loeschweg end-to-end sichtbar (Tracer), Formular- und
Ladeweg nachziehen, Aktionsknoepfe der SUPER_ADMIN-Zeile fuer ADMIN
nicht anbieten. Texte ueber next-intl in de/en, Serverpruefung
unangetastet.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 07:24:05 +02:00