- Kopfzeile unter 640 px nur mit Bildmarke, damit die Dashboard-Reiter
lesbar bleiben (vorher war vom Reiter nur "Das" zu sehen)
- Proxmox-Karten strecken sich nicht mehr auf gleiche Zeilenhoehe
- PBS-Pruefstatus auf Deutsch ("Pruefung fehlgeschlagen"), Rohwert als title
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Gedaempfte muted-Schrift fiele auf 2,75:1 (hell) bzw. 3,0:1 (dunkel),
gemessen; Produktname, Adresse und Fussangabe bleiben deshalb voll lesbar.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Kopfzeile bekommt Einhaengepunkt header-center-slot; auf / entfaellt der Text Startseite
- DashboardTabs rendert per createPortal dorthin, eigene Zeile ueber dem Raster entfaellt
- eingelassene Spur mit erhabenem aktivem Reiter, waagrecht scrollbar mit weicher Randausblendung
- role=tablist/tab, Pfeiltasten/Pos1/Ende, sichtbarer Fokusring, aktiver Reiter wird ins Bild gescrollt
- Plus-Knopf ausserhalb der Spur, Loeschdialog am Dokumentkoerper, Ziehhinweis als sr-only/Tooltip
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Statusfarben als OKLCH-Tokens (Flaeche + kontrastgepruefte Textvariante) und Well-Token
- Gesundheitsbalken mit Legende und vorgelesener Zusammenfassung
- Karten nach Zustand sortiert, Statusleiste, getoenter Schatten, Statuspille
- Messwerte in eingelassenen Feldern: PVE-Knoten mit Balken, PBS-Fuellstand/Sicherung/Pruefung, PMG-Zahlfelder
- offline & verwaist: gestrichelt, gedaempft, keine alten Messwerte
- Skelett-Karten beim Laden, Leerzustand als Well, relative Zeitangaben
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- serverHealth mit Vorrang fuer offline & verwaist (isActive=false)
- THRESHOLDS als einzige Schwellenquelle, meterLevel fuer Balken
- Sortierung/Zusammenfassung je Zustand, formatAge ueber Intl.RelativeTimeFormat
- null erzeugt nie eine Warnung (24 Tests)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
html-to-image laedt jedes <img> per fetch nach; ein einziges Bild ohne CORS
(z. B. ein direkt von der Website geholtes Favoriten-Symbol) liess die ganze
Aufnahme scheitern, im Dialog blieb das Haekchen "Bildschirmfoto beifuegen"
gesperrt. Nicht ladbare Bilder werden jetzt zum transparenten Pixel; scheitert
die Aufnahme trotzdem, folgt ein zweiter Versuch ohne Bilder und Rahmen.
Im Linux-Client 1.3.1 nachgestellt und nach dem Fix gegengeprueft.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Bei kurzen Linknamen blieb rechts viel Leerraum; minW 3 -> 1. Der Titel
kuerzt mit Auslassungszeichen, das Symbol bleibt stehen.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Zwei weitere Nachbesserungen aus dem Rundgang zu 260923-dhh, beide in
ServerCard.tsx und deshalb in einem Commit:
Befund 2: ein frisch angelegter, noch nie abgefragter Server zeigte
faelschlich "Ein unerwarteter Fehler ist aufgetreten" — die leere
Zwischenlagerzeile aus createServer hat `reachable: false` und
`errorKind: null`, was bisher blind in die Fehler-Uebersetzung `unbekannt`
lief. Neuer ruhiger Zustand fuer `status.lastPolledAt === null`, der auf
"Jetzt aktualisieren" verweist; die bestehenden Fehlermeldungen (inkl.
`unbekannt` fuer echte unbekannte Fehler) bleiben fuer `lastPolledAt !== null`
unveraendert.
Befund 3: die Klasse `uppercase` sass auf der ganzen Statuszeile und faerbte
dadurch auch die Adresse gross ("PVE — HTTPS://..."). Jetzt nur noch auf dem
Produktkuerzel.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Nachbesserung aus dem Browser-Rundgang zu 260923-dhh (Befund 1): "Verbindung
testen" pruefte bislang immer den in der Datenbank gespeicherten Server, nicht
das ungespeicherte Formular. Eine im Formular abgeschaltete Zertifikatspruefung
oder ein neu eingetipptes Token-Geheimnis wurden dadurch beim Test ignoriert
und erst nach "Speichern" wirksam — eine Falle fuer genau den Ablauf, den
Nutzer instinktiv waehlen (eintippen, testen, dann erst speichern).
Neues `TestProxmoxServerDto` plus Merge-Baustein `resolveEffectiveTestServer`
in `ProxmoxService`: normale Felder folgen dem Formular (auch wenn absichtlich
geleert), Geheimnisfelder folgen der bestehenden "leer -> gespeicherten Wert
behalten"-Regel, weil `ServerForm` sie beim Laden nie aus der Datenbank
vorbefuellt. Neue Route `POST servers/test` (ohne `:id`) deckt die Neuanlage
ab, wo es noch keinen gespeicherten Server gibt. Der Testen-Knopf steht jetzt
immer zur Verfuegung, nicht mehr nur nach dem ersten Speichern.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- ServerCard.tsx: einzige Stelle, die einen Messwert in Text verwandelt
(formatMetric) — null wird ueberall "unbekannt", nie 0/NaN/leer;
verzweigt ueber productType auf PVE/PBS/PMG; PBS ohne Sicherung zeigt
"noch keine Sicherung" statt eines Fehlers; nicht erreichbarer Server
zeigt Klartext-Ursache plus Zeitpunkt der letzten erfolgreichen Messung
- page.tsx: "Jetzt aktualisieren" fragt alle Server neu ab und laedt die
Liste danach neu, waehrend des Laufs gesperrt; ruhiger Hinweis bei
leerer Liste mit Weg zu den Einstellungen; Link zu den Einstellungen
nur fuer Administratoren sichtbar (Anzeige, kein Zugriffsriegel)
- proxmox-api.ts: ProxmoxMetrics-Union (Pve/Pbs/Pmg) fuer typsichere
Verzweigung im Frontend
- umlaut-dictionary.ts: zwei weitere korrekte Woerter auf die
Positivliste (Messung, Prozessorlast)
Tore: web 708/708 (>=693), type-check 4/4, Biome apps/web 53 Warnungen
(unveraendert).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- proxmox.service.ts: updateServer (Muster LdapConfigService.updateConfig
- nicht gesendet laesst unveraendert, leer loescht, gefuellt
verschluesselt neu; PMG+Token auch beim Bearbeiten abgelehnt, geprueft
gegen den EFFEKTIVEN Stand nach Zusammenfuehren), deleteServer
- proxmox.controller.ts: PUT/DELETE servers/:id, beide zusaetzlich mit
scheduler.refreshTenant() nach dem Schreiben
- Frontend: proxmox-api.ts (updateServer/deleteServer/testServer),
settings/page.tsx (Rollenpruefung nur Anzeige, Serverliste,
Loeschen mit Rueckfrage), ServerForm.tsx (PMG bietet Token gar nicht
an, Geheimnisfelder nie vorbefuellt, Zertifikatspruefung-Schalter
Standard "pruefen", Verbindungstest mit Klartext-Fehlertext)
- umlaut-dictionary.ts: zwei neue, bereits korrekte Woerter
(bewusst/gemessene) auf die Positivliste des Regressions-Waechters
Tore: api 1311/1311 (>=1240), web 701/701 (>=693), type-check 4/4,
lint 5/5, Biome apps/web 53 Warnungen (unveraendert).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
dashboard-tabs.tsx: Ziehen ueber Pointer-Ereignisse (Muster
xframe-config-form.tsx, D-05 - keine neue Abhaengigkeit). Schwelle 4 px
waagerechte Auslenkung trennt Klick von Ziehen; darueber wird der Zeiger
eingefangen (jsdom-Schutzhuelle), die Leiste zeigt die Vorschau-Reihenfolge
(computeReorderedIds, Einfuegen vor dem ersten Nachbarn mit Mittelpunkt
rechts vom Zeiger), beim Loslassen geht die VOLLSTAENDIGE Kennungsliste an
onReorder. Abbruch des Zeigers verwirft die Vorschau ohne zu senden. Ein
hasDraggedRef-Merker unterdrueckt den Klick, der im echten Browser nach
einem Ziehen folgt. Ziehen ist IMMER moeglich, nicht nur im
Bearbeitungsmodus (D-09) - ein Hinweistext erklaert, dass der erste Reiter
beim Oeffnen geladen wird.
dashboard-store.ts: reorderDashboards() setzt die neue Reihenfolge SOFORT
optimistisch, sendet sie und stellt bei einem Fehler die vorherige
Reihenfolge wieder her; der aktive Reiter bleibt aktiv.
dashboard-tabs.test.tsx: 21 Tests (12 alte aus Task 3 + 9 neue fuer jeden
Punkt des Verhaltensblocks). getBoundingClientRect wird je Reiter-Wrapper
ueber data-tab-index gestubbt (Reiter i belegt 100i..100i+100 - jsdom
liefert keine echten Masse). dashboard-store.test.ts: 17 Tests (15 alte +
2 neue fuer Optimismus/Ruecknahme).
Messages: widgets.tabs.dragHint war bereits in Task 3 eingetragen
(vorausschauend) - in diesem Task keine weitere Aenderung an de.json/en.json
noetig.
Gemessene Abweichung von der Plan-Erwartung (kein Rule-1/2/3-Fall, reine
Zahlendifferenz): `grep -c "react-grid-layout|dnd|sortable"
apps/web/package.json` liefert 2 statt der im Plan erwarteten 1 - der
zweite Treffer ist `@types/react-grid-layout`, bereits vor diesem Task
vorhanden (siehe `git diff --stat apps/web/package.json`: keine Aenderung
in keinem der vier Tasks). D-05 (keine neue Zieh-Abhaengigkeit) ist damit
weiterhin erfuellt, nur an der leeren package.json-Diff nachgewiesen statt
an der im Plan vorausgesagten Zahl. Ebenso liefert `pnpm --filter
@tessera/web test` 82 statt der erwarteten 83 Dateien - Task 4 fuegt (siehe
files_modified oben) keine neue Testdatei hinzu, Task 3 hatte die
Dateizahl bereits auf 82 gebracht.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
dashboard-api.ts: fuenf neue Abrufe fuer die Reiter-Wege, die vier
bestehenden Aufrufe (Layout/Widgets lesen/schreiben) tragen jetzt die
Reiter-Kennung (Abfrageparameter bzw. Rumpf).
dashboard-store.ts: Zustand um dashboards/activeDashboardId/
isSwitchingDashboard erweitert. loadDashboard() holt zuerst die Reiter,
macht den ersten aktiv, laedt erst danach dessen Inhalt; ein modul-globales
Versprechen schuetzt gegen doppeltes Laden der Reiterliste bei doppeltem
Einhaengen. selectDashboard() schreibt eine ungespeicherte Anordnung ZUERST
fuer den alten Reiter (Kennung vor dem Wechsel gelesen) und ersetzt danach
Kacheln/Anordnung vollstaendig. createDashboard/renameDashboard/
deleteDashboard pflegen Reiterliste und aktiven Reiter; Loeschen des
aktiven Reiters macht den dann ersten Reiter aktiv. Die Marker-Umrechnung
(quick-260916-bwo) laeuft unveraendert je Reiter mit, auch beim Wechsel.
dashboard-tabs.tsx (neu): Reiterleiste, Klick wechselt immer; im
Bearbeitungsmodus zusaetzlich Anlegen, Umbenennen (an Ort und Stelle,
Enter/Escape) und Loeschen (mit Rueckfrage) - Loeschen-Knopf fehlt beim
letzten verbleibenden Reiter (D-10). Fokus beim Umbenennen ueber einen Ref
statt autoFocus (lint/a11y/noAutofocus).
(portal)/page.tsx: Leiste ueber dem Raster, kurze Ladezeile waehrend eines
Reiterwechsels statt des Rasters - die Leiste bleibt stehen. DashboardGrid
selbst unveraendert (D-06).
Uebersetzungen: neue Schluessel unter widgets.tabs.* in de.json/en.json,
Dialog-Knoepfe nutzen die vorhandenen common.cancel/common.delete.
Deviations (Rule 3 - blockierende Nachwirkung dieses Tasks, ausserhalb der
files_modified-Liste, aber direkt durch die dashboardId-Pflicht verursacht):
- settings/dashboard/page.tsx: fetchWidgets() verlangt jetzt eine
Reiter-Kennung; die Seite ist nicht reiterbewusst (ausserhalb des
Umfangs) und zeigt jetzt die Kacheln des ERSTEN Reiters - deckungsgleich
mit dem bisherigen Verhalten fuer den haeufigen Fall genau eines Reiters.
- (portal)/page.test.tsx: mockStore brauchte die neuen Reiter-Felder/
-Methoden, sonst waere DashboardTabs auf `dashboards.map` von undefined
gescheitert.
Tests: dashboard-store.test.ts 15 (6 alte angepasste Signaturen + 9 neue),
dashboard-tabs.test.tsx 12 (neu). web gesamt 682 Tests in 82 Dateien,
dashboard-grid.test.tsx unveraendert bei 12. type-check 4/4, lint 5/5 mit
weiterhin genau 53 Warnungen in web.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Messung an Ref-Rueckruf measureRef gehaengt statt an Effekt mit leerer
Abhaengigkeitsliste: der Effekt sah den gemessenen Knoten nie, wenn er
waehrend eines leeren Dashboards einhaengt, weil der fruehe Ruecksprung
in den Leerzustand das <div> gar nicht rendert
- Synchrone Erstmessung in der Commit-Phase, vor dem ersten Zeichnen
- Fenster-Horcher als Netz, zusaetzlich zum ResizeObserver
- applyWidth verwirft 0 und nicht endliche Werte (T-VDK-01/T-VDK-03)
- Beobachter trennt sich im null-Zweig des Ref-Rueckrufs (T-VDK-02)
- Zwei neue Regressionstests (Test 10/11), zuerst rot nachgewiesen
- FREE_PLACEMENT_COMPACTOR, Konstanten, Leerzustand unveraendert
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- eine Kachel ohne Bauteil (entfernter Typ oder gesperrtes Modul) zeigt
statt des rohen Typnamens den Satz `widgets.unavailable`, zentriert und
grau; Schluessel in de.json und en.json
- Entwicklerdoku: neuer Abschnitt "Eine Kachel zum Modul" im
Modul-Walkthrough — die drei verbliebenen Stellen und was eine Kachel
mit moduleSlug automatisch tut
- Changelog unter "Unveroeffentlicht - Geaendert"
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ein neuer Widget-Typ war an sieben Stellen einzutragen; vergass man eine,
fehlte die Kachel im Katalog oder die API lehnte sie mit 400 ab.
- WIDGET_TYPES/WidgetType/WIDGET_MODULE_SLUGS stehen jetzt einmal in
packages/shared; Registry, Katalog und die @IsIn-Whitelist der API
leiten davon ab
- neun wireXWidget()-Funktionen durch ein generisches registerWidget()
ersetzt (idempotent, unbekannter Typ wirft in der Entwicklung)
- der Katalog fuehrt keine zweite Typliste mehr, sondern leitet sie aus
der Registry ab und filtert nach Modulzugriff (fail-closed, wenn die
Modulliste unbekannt ist); der Abruf von /modules/active liegt auf der
Dashboard-Seite, nicht im Dialog
- widget-module-map.ts liest die geteilte Tabelle statt einer Kopie, die
oeffentliche Funktion bleibt unveraendert
Der Katalogfilter ist Komfort (T-M1H-01) — verbindlich bleibt der
serverseitige Filter in DashboardService.getWidgets.
Abweichung vom Plan: apps/web hing entgegen der Planannahme noch nicht
von @tessera/shared ab; die Abhaengigkeit wurde ergaenzt (Lockfile). Die
Dockerfiles kopieren packages/shared bereits, der Produktionsbau von
Next.js und der nest build laufen unveraendert.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Befund aus dem Browser-Rundgang: example.com setzt `margin: 15vh` — mit
3000 px Vorschauhoehe lag die Ueberschrift bei y 450, in der Kachel mit
720 px Rahmenhoehe bei y 108. Der in der Vorschau gewaehlte Ausschnitt
zeigte in der Kachel also etwas anderes. Die Kachel nutzt jetzt dieselbe
Layouthoehe wie die Vorschau (3000), damit vh-relative Seiten identisch
umbrechen; der Rest wird ohnehin weggeschnitten.
Vorschau: `overflow-x-hidden`, die Eckgriffe am rechten Rand erzeugten
einen 4-px-Querbalken.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Resolver: crop (x/y/w/h, geklemmt auf 1280er-Seite, x verschoben statt abgewiesen), zoom (50..150, groesste Stufe <= n), readOnly (nur echtes true)
- xframe-crop.ts: reine Geometrie - computeCropLayout (contain + zentriert, scale 0 bis gemessen), applyCropDrag (Verschieben, vier Ecken, Gegenecke bleibt)
- Kachel: Clip + verschobener, skalierter <iframe> bei fester Layoutbreite 1280, ResizeObserver am Koerper; Zoom-Zweig mit Prozentmassen; 100 % wie bisher; readOnly-Flaeche nur im Ansichtsmodus
- Formular: Checkbox Ausschnitt (ein Aufruf mit crop + readOnly), Vorschau 1280 px breit mit derselben Sandbox und pointer-events none, Rahmen mit vier Griffen (Pointer-Events, Capture-Waechter fuer jsdom), Zahlenfelder, Zoom-Auswahl nur ohne Ausschnitt, Nur anzeigen, dauerhafter Hinweis
- Rahmen als <fieldset> statt div role=group (Biome useSemanticElements, kein biome-ignore)
- Test-Helfer stubResizeObserver, 13 neue Schluessel de/en, Ausschnitt auf der Umlaut-Allowlist; Web-Tests 604 -> 640
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Zwei Kosmetik-Punkte nach dem Browser-Rundgang vom 21.09.2026:
- Das laengste Wechselintervall (3600 s) hiess "60 Minuten", beim XFrame
heisst dieselbe Stufe "Jede Stunde". Neuer Schluessel
`pictureFrame.intervalHours` (ICU-Plural, de + en).
- Unter Einstellungen -> Dashboard zeigte "XFrame #1 - Board" seinen Titel,
"Bilderrahmen #1" nichts. Die Kopfzeile nennt jetzt "- 1 Bild" bzw.
"- N Bilder" (Schluessel `imageCountOne`/`imageCountMany`, zwei Schluessel
statt ICU, weil die Panel-Tests eine einfache Uebersetzungs-Attrappe nutzen).
Web-Tests 603 -> 604, type-check und lint unveraendert gruen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- xframe-config.ts: Resolver (https-Pruefung via isHttpsUrl des Bilderrahmens,
Titel bis 100 Zeichen, Neuladen 0/60/300/600/1800/3600 s geklemmt),
XFRAME_SANDBOX ohne allow-top-navigation und allow-modals; 12 Tests zuerst rot
- xframe-widget.tsx: genau ein <iframe> (sandbox, allow="", no-referrer, lazy),
Kopfleiste mit Titel oder Ecksymbol "In neuem Tab oeffnen", Neuladen ueber
key-Wechsel mit Timer-Raeumung, transparente Flaeche im Bearbeitungsmodus
damit die Kachel Ziehgriff bleibt; 12 Tests zuerst rot
- xframe-config-form.tsx: Adresse/Titel mit Uebernahme bei Blur/Enter, http wird
mit Meldung abgewiesen und nicht gespeichert, Intervall-Auswahl, dauerhafter
Hinweis auf verweigertes Einbetten; 8 Tests
- Panel-Zweig samt "— Titel" in der Kopfzeile, Registry (12x12, Fenster-Symbol),
Katalog, Seite, DTO @IsIn, de/en widgets.xframe (15 Schluessel), Umlaut-Allowlist
"neuem"; der Server ruft die Adresse nie ab
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Befunde aus dem Browser-Rundgang am 21.09.2026:
- Die Grossansicht lag in einem `react-grid-item` mit CSS-`transform`; ein
transformierter Vorfahr wird fuer `position: fixed` zum Bezugsrahmen, der
Dialog war deshalb auf die Kachelflaeche (531x216) beschraenkt statt den
Viewport zu fuellen. Jetzt per `createPortal` in `document.body`, wie der
Kalender-Tooltip.
- Wechselintervall "1 Minuten" -> ICU-Plural (`one {# Minute}`), de + en; der
Formular-Test nutzt dafuer den echten `createTranslator` von next-intl auf
der echten de.json statt eines `{n}`-Ersatzes.
- Standardgroesse 8x8 (216 px hoch) war zu flach fuer ein Foto -> 8x12 wie die
Kalender-Vorgabe.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- CHANGELOG.md: erster Stichpunkt unter Unveroeffentlicht -> Neu
- docs/anleitung-anwender.md: Zeile in der Widget-Tabelle, Absatz zur
Bildverwaltung unter Dashboard > Widgets
- umlaut-dictionary.ts: „Webadresse“ und „Bildausschnitt“ als korrektes
Deutsch auf die Erlaubnisliste des Umlaut-Waechters (der volle Web-Testlauf
hatte die beiden neuen ss-Woerter aus de.json gemeldet)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- picture-frame-config.ts: Eintragstyp als Vereinigung (upload | url) in EINER
geordneten Liste, resolvePictureFrameConfig laesst alles ausser https weg
(T-PI9-07), Intervall 0 oder 5..3600 s, pickNextIndex (Zufall nie dasselbe)
- dashboard-images-api.ts: Upload als FormData-Feld image ohne eigenen
Content-Type, 413 -> deutsche Meldung, Proxy-Pfad fuer <img src>
- PictureFrameWidget: Leerzustand, <img referrerPolicy=no-referrer> (Browser
laedt Fremdbilder, Server nie), object-contain/cover, Unterschrift-Streifen,
Wechsel per Timer mit Raeumung, kaputte Bilder verlassen den Umlauf,
Grossansicht nur ausserhalb des Bearbeitungsmodus mit Fokus-Rueckgabe und
Pause des Wechsels; im Bearbeitungsmodus kein Knopf (Karte bleibt Griff)
- PictureFrameConfigForm im WidgetSettingsPanel: Ausschnitt, Intervall,
Reihenfolge, Liste mit Vorschau/Unterschrift/Pfeilen/Entfernen (Upload wird
auch serverseitig geloescht), Datei hochladen, https-Adresse hinzufuegen
- Registry (4x4 min, 8x8 Vorgabe), Katalog, Seite, Uebersetzungen de/en
(widgets.pictureFrame, 28 Schluessel), bestehende Tests auf acht Typen
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
httpntlm (exchange.provider, exchange-inbox.provider): NtlmOptions und
NtlmResponse beschreiben genau das, was uebergeben und gelesen wird. Die
ueberfluessige Zusicherung (httpntlm as any) faellt weg.
Graph-Rueckrufe (exchange.provider :157/:313): AuthProviderCallback aus dem
SDK selbst statt Handannotation - als import type, also ohne den dynamischen
Import zur Laufzeit zurueckzunehmen.
imapflow: streamToBuffer() nimmt Readable statt NodeJS.ReadableStream (alle
drei Aufrufer reichen client.download().content herein, imapflow deklariert
das als Readable) - damit traegt der Typ destroy() und die Zusicherung
faellt. node.parameters?.name war ebenfalls schon getypt.
nodemailer: ResolvedTransport.options wird SMTPTransport.Options; beide
Zweige bauen reine SMTP-Optionen, createTransport() nimmt sie ohne
Zusicherung.
node-forge: die vier let p7: any werden Captured<PkcsEnvelopedData |
PkcsSignedData> - der MITGELIEFERTE Typ. Die Lesestellen grenzen mit
'certificates' in p7 ein statt zuzusichern; verhaltensgleich, weil der
enveloped-Form das Feld fehlt und beide Schreibweisen dann die leere Liste
liefern. cert.siginfo war bereits getypt.
apps/web/src/test/setup.ts: expect.extend(matchers) traegt ohne Zusicherung
- geprueft im echten Typlauf (setup.ts liegt im include von
apps/web/tsconfig.json, mit einem absichtlichen Fehler nachgewiesen).
BEFUND 4 (D-03, gemeldet, NICHT repariert) imap.provider.ts:78 - der
Ausdruck (node as any).disposition?.parameters?.filename liest .parameters
von einer ZEICHENKETTE: imapflow deklariert disposition als string
(imap-flow.d.ts:448), die Parameter liegen in dispositionParameters (:450).
dispositionFilename ist damit zur Laufzeit immer ''. Folge: Outlook-Anhaenge,
die als application/octet-stream kommen, werden ueber den Dateinamen aus
Content-Disposition NICHT erkannt - nur ueber den aus Content-Type. Umbiegen
waere eine Verhaltensaenderung; die Zusicherung bleibt sichtbar stehen.
BEFUND 5 (D-03, gemeldet, NICHT repariert) imap.provider.ts:402 -
requireTLS kommt in imapflow 1.4.3 NIRGENDS vor, weder in ImapFlowOptions
noch im Laufzeitcode (beides durchsucht). Die Option wird still verworfen;
STARTTLS wird durch sie nicht erzwungen. Genau das } as any hat es
verdeckt. Bleibt stehen, damit der Befund in der Zaehlung sichtbar ist.
BEFUND 6 (D-03, gemeldet, Verhalten unveraendert) httpntlm liefert den
Rumpf als Zeichenkette, nicht als Buffer: httpreq setzt ihn nur bei
gesetzter Option binary auf Buffer (httpreq@1.1.1/lib/httpreq.js:391),
keiner der beiden Aufrufer setzt sie. Der Bestand rief unbesehen
.toString('utf-8') auf - das ging nur gut, weil String.toString() sein
Argument ignoriert. Die Testdoppel reichen dagegen wirklich Buffer herein.
NtlmResponse.body nennt jetzt beide Formen, die Fallunterscheidung liefert
fuer jede exakt dasselbe Ergebnis wie zuvor.
Urteil BLEIBT mit Begruendung im Code an allen 15 verbleibenden Stellen:
3x addCronJob (require-Umweg aus 07-04), 5x node-forge (EC-Zweig und
extensions: any[] sind in @types/node-forge nicht beschrieben, 2x null as
any wo die Typen die Bibliothek nachweislich falsch beschreiben), 2x
imap-Befunde oben, 2x tx: any plus 2x Gefolge (Aufgabe 1), 1x
disposition-Befund.
noExplicitAny in apps/api/src: 31 -> 15 (Ausgang 288, Schranke 45), apps/web
1 -> 0. type-check 4/4, lint 5/5 (0 error), apps/api 72/1143, apps/web
73/531, rls-access-inventory 30/30. noNonNullAssertion 56, as unknown as 33,
ts-expect-error/ts-ignore 0/0, Unterdrueckungsmarker 1. biome.json, alle
package.json und pnpm-lock.yaml unveraendert.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
Der alte Test 1 fand den Fehler nur durch Zufall: er las den DOM einmal,
nachdem findByRole den Dialog gemeldet hatte, und traf damit mal den
falschen ersten, meist den richtigen zweiten Commit. Ein Fehlschlag auf
rund siebzehn volle Laeufe.
Die beiden neuen Tests beobachten stattdessen per MutationObserver JEDEN
Commit waehrend des Oeffnens und dulden keinen einzigen, in dem das
Vorschaubild sichtbar ist und das Haekchen aus. Test 14 deckt das erste
Oeffnen ab, Test 15 das erneute Oeffnen nach einem Versand mit
abgewaehltem Haekchen und getipptem Text - dort darf weder der alte
Dankestext noch der alte Text noch ein ausgeschaltetes Haekchen in
irgendeinem Commit auftauchen.
Beide sind gegen den Stand vor dem Fix in fuenf von fuenf Laeufen rot,
danach gruen. Kein retry, kein hoeheres Zeitlimit: die Ursache war nie
blosse Zeit, sondern ein falscher Zustand, den es jetzt nicht mehr gibt.
Test 1 bleibt unveraendert in der Sache und prueft weiterhin dieselbe
Zusicherung.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
Gemessene Ursache des Wackeltests aus CI-Lauf 395, und es ist ein
Produktfehler, kein Testfehler. Ein MutationObserver ueber jeden
DOM-Commit beim Oeffnen protokollierte:
COMMIT dialog=true img=ja box=AUS <- falsch, aber festgeschrieben
COMMIT dialog=true img=ja box=AN
Der erste Zustand entstand bei JEDEM Oeffnen, nicht nur unter Last, und
hielt ohne act() zwei volle Makrotask-Runden - der Browser hat in dieser
Zeit mindestens zwei Gelegenheiten, ihn zu zeichnen. Ein Nutzer sieht
also sein Vorschaubild kurz mit ausgeschaltetem Haekchen. Der Test fiel
nur dann durch, wenn er zufaellig den ersten statt den zweiten Commit
sah; die Last im vollen Lauf war der Ausloeser, nicht die Ursache.
Zwei Bedingungen mussten zusammentreffen. Erstens war der Dialog
dauerhaft eingehaengt und gab bei geschlossenem Zustand nur null zurueck
- useState(screenshot !== null) lief damit ein einziges Mal, beim
allerersten Mount des Knopfs, als noch gar kein Bild da war. Das
Haekchen startete also immer aus. Zweitens zog ein useEffect den
Zustand nach, und passive Effekte laufen erst NACH dem Commit.
Beides ist jetzt weg. Der Dialog wird nur noch eingehaengt, solange er
offen ist, also ist jedes Oeffnen ein frischer Mount mit frischem
Zustand. Und das Haekchen wird beim Rendern aus screenshot abgeleitet
statt per Effekt nachgezogen; attachChoice haelt allein die bewusste
Abwahl des Nutzers. Der Effekt, der Status, Text und Haekchen beim
Oeffnen zuruecksetzte, entfaellt ersatzlos.
Damit verschwindet dieselbe Klasse an einer zweiten Stelle: beim
erneuten Oeffnen nach einem Versand stand bisher zwei Runden lang der
alte Danke-Bildschirm im DOM, bevor das frische Formular erschien.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
Beide Befunde sind nicht die Ursache des Wackeltests aus CI-Lauf 395,
aber beide haetten die Fehlersuche in die Irre fuehren koennen.
Erstens stand ein expect INNERHALB der toPng-Attrappe. Wirft es, landet
der Fehler mitten im await von captureScreenshot, und dessen catch
liefert still null zurueck. Der Test waere dann nicht an der Stelle
durchgefallen, die er prueft, sondern viel spaeter mit der Meldung, es
gebe kein Vorschaubild. Die Attrappe haelt die Beobachtung jetzt nur
noch fest, geprueft wird sie im Testkoerper. Bewiesen wird dasselbe:
zum Zeitpunkt der Aufnahme steht kein role="dialog" im DOM.
Zweitens wurden scrollWidth und scrollHeight von document.body per
Object.defineProperty ueberschrieben und nie zurueckgesetzt. Die eigene
Eigenschaft verdeckt den Getter von Element.prototype, und document.body
ueberlebt cleanup() - alle zwoelf folgenden Tests der Datei sahen
weiterhin 3200x1000. stubBodyGroesse merkt sich das jetzt, afterEach
nimmt es per Reflect.deleteProperty wieder zurueck.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
Restposten 3a (doppeltes Ladefenster): lastFetchWindowRef merkt sich das
zuletzt TATSAECHLICH geholte from/to-Paar; ein Monatswechsel, dessen
berechnetes Fenster damit uebereinstimmt, ueberspringt fetchEvents. Der
5-Minuten-Auffrischer (force=true) umgeht den Sperrgriff immer, sonst
friert die Anzeige ein. computeFetchWindow und die Tagesgrenzen-Rundung
bleiben unangetastet.
Restposten 3b (Quellenliste je Monatswechsel): hasSourcesRef merkt sich
das Ergebnis; fetchSources laeuft nur beim Aufbau (Merkung leer) oder
erzwungen (Auffrischer) — eine neu eingerichtete Quelle wird weiterhin
binnen fuenf Minuten bemerkt.
Restposten 4 (t-Identitaet): neue translations-identity.test.tsx rendert
eine Testkomponente unter dem ECHTEN NextIntlClientProvider und beweist
per Referenzgleichheit, dass t bei einem lokalen Zustandswechsel
dasselbe Funktionsobjekt bleibt — bestaetigt durch den use-intl-4.13.0-
Quelltext (translate entsteht in einem useMemo, dessen Abhaengigkeiten
ausschliesslich aus dem root-staendigen Intl-Kontext stammen). Die
Faustregel aus 260921-gof ("t gehoert in keine Abhaengigkeitsliste")
bleibt als Konvention in Ordnung; die zugrunde liegende Annahme ("t ist
bei jedem Render frisch") ist damit ausdruecklich WIDERLEGT statt ein
drittes Mal weitergetragen. Die vier verbliebenen Stellen
(marketplace/page.tsx, admin/users/page.tsx,
calendar-settings-panel.tsx, calendar-source-form.tsx) bleiben deshalb
unveraendert.
Vier neue zaehlende Testfaelle in calendar-widget.test.tsx (Aufbau je 1,
abweichendes Fenster +1/+0, identisches Fenster +0/+0, erzwungener Lauf
+1/+1) plus ein Test, dass ein uebersprungener Lauf den Ladezustand
sauber beendet und geladene Termine nicht leert.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
downloadAllAsZip liegt ausserhalb der Komponente und kann den
Uebersetzungs-Hook nicht aufrufen; der Name kommt jetzt als Parameter
herein, Aufrufstelle uebergibt t('actions.zipFilename'). Neuer
Schluessel unter certManager.actions: deutsch "Zertifikate.zip",
englisch "certificates.zip".
Der 260921-bi2-Einwand (ein uebersetzter Name koenne Umlaute auf eine
Windows-Freigabe tragen) trifft fuer diesen konkreten Text nicht zu —
das deutsche Wort enthaelt keinen Umlaut. Die Sicherheit haengt darauf
aber NICHT: neue Datei zip-filename.ts mit einer fuer sich pruefbaren
Schutzfunktion, die Windows-verbotene Zeichen, Steuerzeichen und
Nicht-ASCII ersetzt, abschliessende Punkte/Leerzeichen entfernt,
reservierte Geraetenamen abfaengt, bei leerem Ergebnis auf
certificates.zip zurueckfaellt und die .zip-Endung sicherstellt.
SplitTab.tsx schickt den uebersetzten Namen durch diese Funktion, bevor
er am Download landet. Die Dateinamen IM Archiv bleiben unangetastet.
zip-filename.test.ts deckt beide Katalogwerte (unveraendert), Umlaut,
verbotenes Zeichen, Steuerzeichen, abschliessende Punkte/Leerzeichen,
fehlende/vorhandene Endung, leeres Ergebnis und reservierte
Geraetenamen ab.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
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
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
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
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
- 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
- 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
- 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
- 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
- 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
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
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
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
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
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
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