Skip to content

Matrix-Rendering-Fix + Dashboard-Politur-Bündel (Punkte 0–9)#312

Merged
TillQuandel merged 16 commits into
masterfrom
fix/matrix-render-und-politur
Jul 16, 2026
Merged

Matrix-Rendering-Fix + Dashboard-Politur-Bündel (Punkte 0–9)#312
TillQuandel merged 16 commits into
masterfrom
fix/matrix-render-und-politur

Conversation

@TillQuandel

@TillQuandel TillQuandel commented Jul 16, 2026

Copy link
Copy Markdown
Owner

Übersicht

Sammel-PR: Matrix-Rendering-Fix (Punkt 0, Till-Live-Befund) + 9 Politur-Punkte aus den Multi-Perspektiven-Reviews vom 15.07. Je Punkt ein Commit. Basis master 64d8895. Alle Punkte umgesetzt (0 von 10 ausgelassen).

Arbeitsweise: eigener Worktree, isolierter Testserver (Port 8069) mit read-only kopierten Live-Daten (quality_history.jsonl + atomic_analytics.db, Stand 16.07.), Playwright (playwright-core + firefox-1532) für alle UI-Punkte bei 1440px UND 1100px, hell+dunkel. TDD mit RED-Nachweis für die Datenlogik-Punkte (2, 4, 5, 6, 7).


Punkt 0 — Matrix-Rendering kaputt (Till-Live-Befund, PFLICHT)

Befund-Quelle: Till-Live-Server, Screenshots matrix-sicht-4.3.png (eval_version 4.3 = 6 PDFs × 1 Version v0.3.144).

Ursache (per Repro auf isoliertem Testserver + Computed-Style-Diagnose bestätigt, nicht geraten):

  1. Die >=1201px-Sticky-Regel table.cmp thead th (Dashboard responsive: 1100px verschluckt Zeitstempel, Tabellen ohne Horizontal-Scroll, Sticky-Leiste verdeckt Tabellenköpfe #203 P3) trifft über die gemeinsame .cmp-Klasse AUCH table.pm-matrix. Der bestehende Code-Kommentar ("sticky entfällt für die Matrix ohnehin, da der pm-scroll-Wrapper ihr eigener Containing-Block ist") war ein Trugschluss: position: sticky verschiebt die Box bereits im Ruhezustand — ganz ohne zu scrollen — um den top-Offset (49px), sobald die statische Ausgangsposition die Constraint verletzt. Diagnose vor Fix: thPosition: sticky, thRect.y=249 überlappt firstCellRect (233–298) — exakt der beobachtete Versatz in Zeile 1.
  2. table.cmp { width: 100% } stretcht bei wenigen Spalten (hier: 1 Version) die Restbreite in die einzige Datenspalte → riesige Leerfläche zwischen PDF-Spalte und Werten, Header klebt rechts.

Fix:

  • .table-wrap.pm-scroll table.cmp thead th { position: static; box-shadow: none; border-bottom: 1px solid var(--hair); } — Spezifität (0,3,3) schlägt die Media-Query-Regel (0,1,3).
  • table.cmp.pm-matrix { width: auto; } — Matrix schrumpft auf Inhaltsbreite; bei vielen Versionen (16 Spalten) bleibt der bestehende .pm-scroll-Mechanismus unverändert wirksam.
  • Minor: „1 Versionen" → „1 Version" (Singular/Plural), gleiche Zeile/Technik für „PDF-Quelle(n)" mitgezogen.

Nachweis: HTML-Anker-Tests (test_dashboard_version_pdf_matrix.py, 3 neue Tests) + Playwright-Repro VOR/NACH Fix (Screenshot + computed styles: thPosition sticky→static, Überlappung aufgelöst, tableWidth 1162→336px). Screenshots: final-02-matrix-1440-light.png, final-02-matrix-1100-dark.png u. a.


Punkt 1 — eval_version-Dropdown (Till-Wunsch, PFLICHT + Zusatzbefund)

Befund-Quelle: Till-Wunsch + Zusatzbefund während der Umsetzung: ?eval_version=4.1 in der URL hatte im Browser KEINE Wirkung — der Client-Fetch von /data.json reichte den Param nie durch (Bestands-Kommentar: „Eval-Version-Filter entfernt: nie aus URL/State setzen"), obwohl der Server den Query-Param bereits korrekt verarbeitete. Verifiziert: Seite zeigte unter beiden URLs identisch 4.3.

Design-Iteration (Till-Feedback nach erster Sichtprüfung): Die ursprüngliche Umsetzung hängte das Select als eigenes Filterbar-Element an — brach deren Einzeilen-Layout bereits bei 1440px (6. Filter-Group + Warn-Badge passten nicht mehr in eine Zeile). Filterbar-Variante verworfen zugunsten eines Inline-Selects am bestehenden, dezenten eval_version-Meta-Label in der Sidebar (#sf-ev-select, ersetzt den reinen Text-Pill) — kein zusätzliches Filterbar-Element mehr. #pm-evalver (Matrix-Sektion) bleibt Read-only-Text und folgt der Auswahl unverändert (SSoT).

Fix:

  • Server: available_eval_versions trägt jetzt {version, n} statt nur den String — n = Zeilenzahl je Version, ungefiltert (Anzeige „4.3 (n=27)").
  • Client: bereits vorhandene, aber verwaiste Funktionen (onEvalVerChange, _initEvalVerDropdown, _getEvalVerFromUrl) wieder verdrahtet statt neu gebaut — _currentEvalVersion wird beim Boot aus der URL geseedet, loadAndRender() hängt eval_version an JEDEN /data.json-Fetch (inkl. 15s-Auto-Refresh, da derselbe Codepfad). /live.json (separate Pipeline-Progress-Seite ohne eval_version-Konzept) bewusst NICHT angefasst.
  • Sichtprüfungs-Fund (Klick-Pfad 4.3→4.1→zurück): Select-Wert blieb nach manuellem Wechsel auf dem alten Stand — der Post-Fetch-Early-Return in _initEvalVerDropdown griff fälschlich, weil onEvalVerChange() _currentEvalVersion schon vor dem Fetch setzt. Rebuild jetzt nur noch an die Optionsmenge gekoppelt, Auswahl-Sync immer.

Nachweis: Server-Tests (Zeilenzahl je Version, filterunabhängig), HTML-Anker-Tests (kein Filterbar-Element, Sidebar-Select vorhanden), Node-Ausführungstest für den Select-Wert-Sync-Fund. Playwright-Klick-Pfad: Dropdown zeigt „1.3 (n=87) / 4.1 (n=515) / 4.3 (n=27)", Wechsel 4.3→4.1→zurück aktualisiert Kopfzeile+Matrix+Select korrekt, Deep-Link ?eval_version=4.1 greift, Auswahl bleibt nach 17s (>1 Auto-Refresh-Zyklus) stabil auf 4.1, Filterbar wieder einzeilig bei 1440px UND 1100px (hell+dunkel), 0 pageerrors. Screenshots: final-03/04-clickpath-eval-*.png, 01e/01f-sidebar-*.png.


Punkt 2 — n-valid-Badge je Modell-Filter-Option

Befund-Quelle: Reviews 15.07.

Fix: Modell-Dropdown-Optionen zeigen jetzt die Anzahl VALIDER Eval-Zeilen (hallucination_rate >= 0) je Modell — Gemini-Fehllesungs-Schutz: dort tragen Zeilen den bestehenden -1.0-Sentinel; n_valid zeigt korrekt 0, nicht die volle aber wertlose Zeilenzahl. note_evals trägt kein eigenes model-Feld (lebt in pipeline_runs) — Join über run_idtoken_runs.model, derselbe Mechanismus wie der bestehende Modell-Einzelwert-Filter. Basis: _matrix_base_rows (eval_version-skopiert, vor allen Einzelwert-Filtern) — dieselbe „Dropdown-Optionen vor allen Filtern"-Konvention wie die Options-Liste selbst.

all_models wechselt von list[str] auf list[{model, n_valid}] (kein bestehender Konsument referenzierte die alte Form).

Nachweis: 4 Server-/HTML-Anker-Tests (Sentinel-Ausschluss, None nicht als valide gezählt, Denylist-Regression, JS-Anzeige). Playwright: Dropdown zeigt z. B. „anthropic/claude-sonnet-4-6 (n=27)" neben mehreren „(n=0)"-Modellen (korrekt, da eval_version 4.3 nur diesem einen Modell Daten liefert). Screenshot final-05-modell-dropdown-offen.png.


Punkt 3 — Accept×Hall-Overlay je Version (S2-C3)

Befund-Quelle: Statistik-Review 15.07.: note_evals enthält nur AKZEPTIERTE Notes (Selektions-Bias) — sinkt die Akzeptanzquote gleichzeitig mit einer Fehlerquoten-Änderung, ist Letztere teils Stichproben-Verschiebung statt reiner Qualitätseffekt, ohne dass das im bestehenden Chart sichtbar wäre.

Fix: Akzeptanzquote als zweite, gestrichelte/gedämpfte Serie im BESTEHENDEN Fehlerquote-Spark-Chart (Klick auf die Fehlerquote-KPI-Kachel, _trendChart) — kein neues Chart gebaut (Spec-Vorgabe). _trendChart bekommt zwei optionale Parameter (points2, label2); Y-Achse deckt beide Serien gemeinsam ab, nur bei gleicher Einheit (%) — sonst ergibt eine gemeinsame Skala keinen Sinn (z. B. Dauer-Kachel bleibt unverändert). Zweite Serie bewusst schlank: eigene kleinere Punkte, aber ohne Hover-Tooltip-Kästen (Zweck ist der visuelle Trend-Vergleich); kleine SVG-Legende oben links.

Nachweis: Node-Ausführungstest der echten _trendChart-Funktion (6 Tests: Overlay rendert bei points2, fällt bei fehlendem points2/nicht-%-Einheit/Längen-Mismatch sauber weg, gemeinsame Skala deckt Werte außerhalb des Fehlerquote-Bereichs ab, Aufrufer-Verdrahtung nur für die Fehlerquote-Kachel). Playwright (eval_version 4.1, 15 Pipeline-Versionen, hell+dunkel): zwei klar unterscheidbare Linien auf gemeinsamer 0–100%-Achse, Legende sichtbar, 0 pageerrors. Screenshot 03-accept-hall-overlay*.png.


Punkt 4 — accept-Delta-Guard auf geroutete statt evaluierte Notes (D3)

Befund-Quelle: Statistik-Review 15.07.

Fix: version_delta() lief für ALLE KPI-Metriken mit demselben kpi_trend["n"]-Array (evaluierte Notes) als n≥20-Reliability-Guard. Für „accept" (Akzeptanzrate, gepoolt über ALLE gerouteten/generierten Notes) ist das der falsche, zu kleine Nenner — Bug ist latent: auf dem aktuellen Bestand kippt kein einziges accept-Delta von unreliable zu reliable (adversarialer Audit bestätigt: kein Delta wechselt den Zustand) — die sichtbare, verifizierte Wirkung ist ein korrigierter Tooltip-Nenner am Delta-Chip (das „n=" in „Version X (n=…)" wechselt von der Zahl evaluierter auf die Zahl gerouteter Notes, konkret 40→64 bzw. 10→4 in den geprüften Fällen). Neuer optionaler Parameter n_field (Default "n", rückwärtskompatibel); Server übergibt für „accept" n_field="accept_n" — neues kpi_trend-Feld, Summe derselben (n_vault, n_total)-Paare wie _pooled_accept (SSoT).

Nachweis: RED zeigte den Bug direkt (accept-Delta reliable=False bei n=8/9 evaluiert, obwohl 150/160 geroutete Notes vorlagen). 5/5 grün nach Fix, inkl. Rückwärtskompatibilitäts- und Server-Integrationstest.


Punkt 5 — coverage_factual-or-Anti-Pattern an Bestandsstellen (D4-Bestand)

Befund-Quelle: Statistik-Review 15.07. Der #305-Helper _row_coverage() fixte das coverage_factual or coverage_rate-Anti-Pattern (verschluckt eine ECHTE 0.0 als falsy, reale 0%-Coverage-Zeilen existieren) bisher nur für die neuen Matrix-Funktionen — Docstring markierte die Bestandsstellen explizit als separates Ticket.

Fix: 7 Bestandsstellen auf _row_coverage umgestellt (_calc_kpis, _calc_pdf_table, _chart_scatter, _build_quality_chart_data in eval_dashboard.py; _read_calibration_data, quality_by_version-Aggregation, _chart_scatter_versioned in eval_dashboard_server.py). _read_calibration_data arbeitet auf sqlite3.Row (kein .get()) — _row_coverage dafür Mapping-kompatibel gemacht (in r.keys() statt .get()) statt die Logik zu duplizieren. Nicht angefasst (geprüft, nicht blind ersetzt): die „Routing-only"-Zeilen in _calc_pdf_table setzen cov: None hartkodiert (keine Eval-Daten vorhanden).

Nachweis: 10 RED-Tests (8 zeigen 90.0 == 0.0, also das Verschlucken; 2 für die sqlite3.Row-Kompatibilität), alle grün nach Fix.


Punkt 6 — Dropped-Zählung ehrlich „–" statt stiller 0 (D5)

Befund-Quelle: Statistik-Review 15.07.

Fix: n_dropped existiert strukturell NUR im DB-Fallback-Pfad (pipeline_runs-Tabelle). Der primäre Log-Pfad (_read_all_log_runs) baut Zeilen aus [DRY-RUN]->(Vault|Inbox)-Treffern — verworfene Kandidaten hinterlassen dort GAR KEINE Zeile, sind also nicht ermittelbar, nicht nur zufällig 0. _calc_kpis liefert jetzt total_dropped=None, wenn keine Zeile den Key n_dropped trägt; Client zeigt „–" mit angepasstem Tooltip statt der Zahl. Bestehender Test, der die alte (== 0) Erwartung kodierte, aktualisiert (nicht gelöscht, Docstring-Verweis ergänzt).

Nachweis: RED zeigte 0 is None an 2 Stellen + fehlenden HTML-Anker. 5/5 grün nach Fix.


Punkt 7 — Spark-Achsen-Eskalation für %-/h-Einheiten (#292-LOW)

Befund-Quelle: Reviews 15.07.

Fix: Die Nachkommastellen-Eskalation bei kollidierenden Spark-Hilfslinien-Labels nutzte eine zweite, unit-fremde Formel, die zufällig zu „$" passte, aber nicht zu „%" (Basis-Anzeige hängt gar kein „%" an — inkonsistent sobald eskaliert wurde) oder „h" (< 1h zeigt „N min", Eskalation erzwang rohe Stunden-Dezimalen — Einheiten-Sprung). _autoUnits fmt-Funktionen nehmen jetzt einen optionalen Nachkommastellen-Parameter; die Eskalation ruft au.fmt(v, extra) — SSoT, jede Einheit eskaliert in ihrer eigenen Konvention. „%" hängt neu auch in der Basis-Anzeige den Suffix an.

Nachweis: Node-Ausführungstest der echten JS-Funktionen — 2 RED-Failures vor dem Fix (fehlender %-Suffix, Einheiten-Sprung bei h), 9/9 grün danach inkl. Mischfall-Regressionswächter (Wertespanne straddled die 1h-Grenze → weiterhin korrekt gemischte min/h-Einheiten, kein Bug).


Punkt 8 — ch3/leg3 Rohkey-Labels (#294-Nebenfund)

Befund-Quelle: Reviews 15.07.

Fix: _chart_scaling() (Chart 3 „Scaling" + Legende 3) kopierte r["label"] unverändert — im primären Log-Pfad ist das _PDF_LABELS.get(key, key), fällt für PDFs außerhalb der 3 registrierten Einträge (Regelfall) auf den rohen, kleingeschriebenen Log-Key zurück. Dieselbe Bugklasse traf bereits Chart 2 (dort per U4-Fix lokal gelöst) — jetzt dieselbe Formel in _chart_scaling, aber NUR wenn label==key (der Fallback tatsächlich griff), ein bereits besserer Label-Wert aus dem DB-Fallback-Pfad bleibt unangetastet.

Nachweis: 4 RED-Tests zeigten den rohen Key, GREEN danach inkl. Regressionswächter für den DB-Fallback-Fall.


Punkt 9 — Responsive-Bruch bei 1100px (U8/U9)

Befund-Quelle: UX-Review 15.07.

U8: kpis-perf (5 Kacheln) ist durch keine der beiden Breakpoint-Spaltenzahlen (4 bzw. 2) teilbar — die letzte Kachel stand allein in der letzten Zeile mit leeren Spalten daneben. Fix: letzte Kachel spannt per grid-column: 1 / -1 die volle Breite, wenn sie allein in ihrer Zeile steht (an beiden Breakpoints).

U9: Das Modell-Filter brach als letzte Filter-Group allein in eine neue Zeile. Kompaktere Gruppierung durch engeres .filter-group-Padding (14px→10px) und schmalere .fselect-Maximalbreite (200px→150px) bei ≤1200px. Bei 1100px bleibt „Modell" trotzdem allein in Zeile 2 — die reine Inhaltsbreite der übrigen 4 Groups reicht nicht für alle 5 in eine Zeile, ohne PDF-/Lauf-Namen bei echten Filterwerten unlesbar zu stauchen — aber jetzt kompakt statt mit großem Leerraum, kein Bruch mehr. Offener Punkt: volle Einzeiligkeit bei 1100px nicht erreicht (Kompromiss zugunsten der Lesbarkeit von Lauf-/PDF-Namen bei realen Werten).

Nachweis: HTML-Anker-Tests für beide Breakpoint-Blöcke. Playwright 1100px hell+dunkel: KPI-Zeile ohne Lücke, Filterbar-Umbruch kompakt statt mit großem Leerraum, 0 pageerrors.


Test-/Lint-Status

  • Volle Suite pytest generative lib/decision_engine/tests shared/tests -q: 6091 passed, 3 skipped, 8 deselected (Baseline vor dieser Serie: 6035 passed — 56 neue Tests, 0 Failures, 268s).
  • ruff check . + ruff format --check .: alle Dateien grün.
  • NUL-Byte-Check nach jedem Edit an eval_dashboard.html: durchgehend genau 1 (bewusst).
  • Sichtprüfung: Playwright (playwright-core + firefox-1532), Testserver 8069, read-only kopierte Live-Daten. 1440px + 1100px, hell+dunkel. Klick-Pfade: eval_version-Dropdown 4.3→4.1→zurück (>16s Stabilität über Auto-Refresh geprüft), Modell-Dropdown (n-Badges), Versions-Paar-Dropdowns (Matrix-Sektion). 0 pageerrors über alle Läufe.

Kein Merge — externe Reviews (Statistiker + UX/UI) folgen.

Tilltime added 16 commits July 16, 2026 10:53
Till-Live-Befund auf master 64d8895 (eval_version 4.3 = 6 PDFs x 1
Version v0.3.144): Der Versions-Spaltenkopf "v0.3.144" rendert nicht in
der Kopfzeile, sondern ~49px tiefer und ueberlappt Zeile 1 (Bates), die
Kopfzeile selbst wirkt leer.

Ursache (per Repro auf isoliertem Testserver mit read-only kopierten
Live-Daten + Computed-Style-Diagnose bestaetigt, nicht geraten): die
>=1201px-Sticky-Regel `table.cmp thead th` (#203 P3) trifft ueber die
gemeinsame .cmp-Klasse auch table.pm-matrix. Der bestehende Kommentar
("sticky entfaellt fuer die Matrix ohnehin, da der pm-scroll-Wrapper ihr
eigener Containing-Block ist") war ein Trugschluss: position:sticky
verschiebt die Box bereits im Ruhezustand (ganz ohne Scrollen) um den
top-Offset, sobald die statische Ausgangsposition die Constraint
verletzt -- exakt der beobachtete 49px-Versatz.

Zusaetzlich klebte die einzige Versions-Spalte am rechten Tabellenrand
mit grosser Leerflaeche zur PDF-Spalte: `table.cmp { width:100% }`
stretcht bei wenigen Spalten die Restbreite in die einzige Datenspalte.

Fix:
- `.table-wrap.pm-scroll table.cmp thead th` setzt position/box-shadow/
  border-bottom explizit zurueck (Spezifitaet 0,3,3 schlaegt 0,1,3).
- `table.cmp.pm-matrix { width: auto }` laesst die Matrix auf
  Inhaltsbreite schrumpfen -- Datenspalten ruecken links neben die
  PDF-Spalte, bei vielen Versionen bleibt der bestehende
  Scroll-Mechanismus (.pm-scroll) unveraendert wirksam.
- Minor: Insight-Text zeigte "1 Versionen" statt "1 Version";
  Singular/Plural-Fix gleich fuer "PDF-Quelle(n)" mitgezogen (identische
  Zeile/Technik).

Nachweis: HTML-Anker-Tests (_build_live_html-Muster) fuer CSS-Reset,
width:auto und die Singular/Plural-Ternaries; Playwright-Repro vor/nach
Fix (Screenshot + computed styles: thPosition sticky->static, thRect/
firstCellRect-Ueberlappung aufgeloest, tableWidth 1162->336px) auf
isoliertem Testserver mit read-only kopierten Live-Daten (eval_version
4.3).
Till-Wunsch + Zusatzbefund: Das Dashboard defaultete unveraenderlich auf
die neueste eval_version (4.3, 27 Baseline-Zeilen). Der URL-Param
?eval_version=4.1 hatte im Browser KEINE Wirkung -- der Client-Fetch von
/data.json reichte ihn nie durch (Bestands-Kommentar: "Eval-Version-
Filter entfernt: nie aus URL/State setzen"), obwohl der Server den Query-
Param bereits korrekt verarbeitete. Verifiziert: Seite zeigte unter
beiden URLs identisch 4.3.

Die Client-Funktionen fuer ein Eval-Version-Dropdown (onEvalVerChange,
_initEvalVerDropdown, _getEvalVerFromUrl/_setEvalVerInUrl) existierten
bereits verwaist im Code (Markup + Fetch-Wiring waren entfernt) --
wieder angeschlossen statt neu gebaut:

- Filterbar: neues Dropdown "Eval-Version" (Optionen aus
  available_eval_versions, Anzeige "4.3 (n=27)"), Warn-Badge bei >1
  Version.
- Server: available_eval_versions traegt jetzt {version, n} statt nur
  den Versions-String -- n = Zeilenzahl je Version, ungefiltert
  (dieselbe "Optionen vor allen Filtern"-Konvention wie all_pdfs_opts).
- loadAndRender() haengt eval_version an JEDEN /data.json-Fetch (inkl.
  15s-Auto-Refresh, da derselbe Codepfad) -- /live.json bewusst NICHT
  angefasst: separate, eval_version-fremde Live-Run-Progress-Seite ohne
  eval_version-Konzept im Payload.
- Sichtpruefungs-Fund (Klick-Pfad 4.3->4.1->zurueck): sf-ev-pill blieb
  nach manuellem Wechsel auf dem alten Wert stehen -- der Post-Fetch-
  Early-Return in _initEvalVerDropdown griff faelschlich, weil
  onEvalVerChange() _currentEvalVersion schon VOR dem Fetch setzt.
  Rebuild jetzt nur noch an die Optionsmenge gekoppelt, pill/warn immer
  aktualisiert.
- Sidebar: persistenter eval-Pill (#sf-ev-pill) neben #sf-time,
  konsistent mit #pm-evalver in der Matrix-Sektion (beide aus
  d.eval_version derselben Antwort).

Nachweis: Server-Tests (Zeilenzahl je Version, filterunabhaengig, Query-
Param-Aufloesung), HTML-Anker-Tests (_build_live_html-Muster) fuer
Markup/Fetch/Boot-Seed/Dropdown-Wiring, Node-Ausfuehrungstest fuer den
Pill-Guard-Fund. Playwright-Klick-Pfad auf isoliertem Testserver (Live-
Daten read-only): Dropdown zeigt "1.3 (n=87)/4.1 (n=515)/4.3 (n=27)",
Wechsel 4.3->4.1->zurueck aktualisiert Kopfzeile+Matrix+Pill korrekt,
Deep-Link ?eval_version=4.1 greift, Auswahl bleibt nach 17s (>1 Auto-
Refresh-Zyklus) stabil auf 4.1, 0 pageerrors.
…efixt

D4-Bestand (Reviews 15.07.): der #305-Helper `_row_coverage()` fixte das
`coverage_factual or coverage_rate`-Anti-Pattern (verschluckt eine ECHTE
0.0 als falsy, 0%-Coverage-Zeilen existieren real, Jockisch-Faelle) bisher
nur fuer die neuen Matrix-/Paarvergleichs-Funktionen -- der Docstring
markierte die Bestands-Stellen explizit als separates Ticket.

7 Bestandsstellen mit demselben Muster auf _row_coverage umgestellt:
- eval_dashboard.py: _calc_kpis (avg_cov-KPI-Kachel), _calc_pdf_table
  (cov je PDF-Zeile), _chart_scatter + _build_quality_chart_data
  (Legacy-main()-Standalone-Pfad).
- eval_dashboard_server.py: _read_calibration_data (llm_cov),
  quality_by_version-Aggregation (kpi_trend.cov-Sparkline),
  _chart_scatter_versioned (aktiver Scatter-Chart).

_read_calibration_data arbeitet auf sqlite3.Row (kein `.get()`) --
_row_coverage dafuer Mapping-kompatibel gemacht (`in r.keys()` statt
`.get()`, funktioniert fuer dict UND Row identisch) statt die Fallback-
Logik an dieser einen Stelle separat zu duplizieren.

Nicht angefasst (Suche + Pruefung, nicht blind ersetzt): die
"Routing-only"-Zeilen in _calc_pdf_table setzen `cov: None` hartkodiert
(keine Eval-Daten vorhanden, kein Anti-Pattern moeglich).

Nachweis (TDD): 8 RED-Tests je Bestandsstelle (assert 90.0 == 0.0, zeigt
das Verschlucken) + 2 fuer die sqlite3.Row-Kompatibilitaet von
_row_coverage selbst, alle 10 vor dem Fix rot, danach gruen. Sweep
generative/tests -k "dashboard or coverage or calib": 310 passed, keine
Regression.
…es umgestellt

D3 (Reviews 15.07.): version_delta() lief fuer ALLE KPI-Metriken (hall/cov/
n/accept/dur/tokens/cost) mit demselben kpi_trend["n"]-Array als n>=20-
Reliability-Guard. Fuer hall/cov ist das korrekt (n = evaluierte Notes,
das SIND Eval-Metriken). Fuer "accept" (Akzeptanzrate, gepoolt ueber ALLE
gerouteten/generierten Notes, _pooled_accept) ist n der falsche, zu kleine
Nenner -- nur eine Stichprobe wird LLM-evaluiert. Bug war "aktuell
konservativ": kein falsches Delta, aber unnoetig graue/unreliable Chips,
obwohl das Accept-Delta auf hunderten gerouteten Notes beruht.

Fix: version_delta() bekommt optionalen n_field-Parameter (Default "n" --
rueckwaertskompatibel fuer hall/cov/dur/tokens/cost). Server uebergibt fuer
"accept" n_field="accept_n" -- neues kpi_trend-Feld, Summe derselben
(n_vault, n_total)-Paare wie _pooled_accept (SSoT, keine zweite Zaehlung).

Nachweis (TDD): RED zeigte den Bug direkt (accept-Delta reliable=False bei
n=8/9 evaluiert, obwohl 150/160 geroutete Notes vorlaegen) sowie den
fehlenden n_field-Parameter/accept_n-Key. Nach Fix: 5/5 gruen, inkl.
Rueckwaertskompatibilitaets-Test (hall-Delta ohne n_field unveraendert) und
Server-Integrationstest (accept_n aus denselben Log-Runs wie
_pooled_accept). Sweep generative/tests -k "dashboard or delta or accept":
325 passed, keine Regression.
…Pfad

D5 (Reviews 15.07.): "n_dropped" existiert strukturell NUR im DB-Fallback-
Pfad (eval_dashboard_server.py, pipeline_runs-Tabelle). Der primaere
Log-Pfad (_read_all_log_runs()) baut Zeilen aus [DRY-RUN]->(Vault|Inbox)-
Treffern -- verworfene Kandidaten (nie bis zum Draft gekommen) hinterlassen
dort GAR KEINE Zeile und tauchen im notes-Dict nie auf. `n_dropped` ist an
dieser Quelle nicht zufaellig leer, sondern nicht ermittelbar. Die Kachel
zeigte trotzdem eine stille "0" (`r.get("n_dropped", 0)`), ununterscheidbar
von "wirklich 0 verworfen" -- die bestehende Tooltip-Erklaerung deckte das
zwar schon auf, aber der angezeigte WERT selbst log weiter.

Fix: `_calc_kpis` liefert `total_dropped=None`, wenn KEINE Zeile in
all_log_runs den Key "n_dropped" traegt (Primaer-Pfad/leere Runs) -- die
Summe (inkl. echter 0, wenn die DB-Spalte 0 ist) bleibt unveraendert, sobald
mindestens eine Zeile den Key traegt (DB-Fallback-Pfad). Client zeigt "–"
statt der Zahl, Tooltip-Text an das neue Verhalten angepasst.

Bestehender Test `test_calc_kpis_total_dropped_zero_when_field_missing`
kodierte selbst das Anti-Pattern (`assert ... == 0` bei fehlendem Key) --
auf die korrigierte Erwartung (`is None`) aktualisiert, nicht geloescht
(Historie/Docstring-Verweis auf den D5-Fund ergaenzt).

Nachweis (TDD): RED zeigte `0 is None`-Fehlschlaege an allen 3 betroffenen
Stellen (kein Key, leere Runs, HTML-Anker fuer den JS-Ternary) vor dem Fix.
Nach Fix 5/5 gruen + aktualisierter Bestandstest gruen. Sweep
generative/tests -k dashboard: 312 passed, keine Regression.
…stent zur $-Eskalation

#292-LOW (Reviews 15.07.): Die Nachkommastellen-Eskalation der Spark-
Hilfslinien (_trendChart, kollidierende Achsenlabels bei enger Wertespanne)
nutzte eine zweite, unit-fremde Formel (`_fmtDE(v, extra) + au.u`) statt
der Basis-Formatierungsfunktion. Das passte zufaellig fuer "$"/generische
Einheiten (deren Basis-fmt strukturell identisch ist), aber nicht fuer:

- "%": Basis-Anzeige (_autoUnit) haengt GAR KEIN "%" an ("3,0" statt
  "3,0%") -- die Eskalationsformel dagegen IMMER (au.u="%"). Erst bei
  einer Werte-Kollision sprang das %-Zeichen ploetzlich auf, inkonsistent
  zur unmittelbar daneben unveraenderten Basis-Anzeige.
- "h": Basis-Anzeige zeigt < 1h als gerundete Minuten ("2 min"), >= 1h als
  Stunden-Dezimalzahl ("1,2 h"). Die Eskalationsformel ignorierte das
  komplett und zeigte bei einer Kollision IMMER rohe Stunden-Dezimalen
  ("0,03h") -- ein Einheiten-Sprung weg von der Minuten-Konvention direkt
  daneben.

Fix: `_autoUnit`s fmt-Funktionen nehmen jetzt einen optionalen Nachkomma-
stellen-Parameter (Default = bisheriges hartkodiertes Verhalten je Zweig);
die Eskalationsschleife ruft `au.fmt(v, extra)` statt der zweiten Formel --
SSoT, jede Einheit eskaliert automatisch in ihrer eigenen Konvention.
"%" haengt dabei neu auch in der Basis-Anzeige den Suffix an (identisch zur
Eskalation). Mischfall (Wertespanne straddled die 1h-Grenze nach Padding)
zeigt weiterhin korrekt gemischte min/h-Einheiten -- kein Bug, dieselbe
Logik wie die unveraenderte Basis-Anzeige, per Test abgesichert.

Nachweis (TDD, Node-Ausfuehrung der echten JS-Funktionen, kein Nachbau):
2 RED-Failures vor dem Fix (fehlender %-Suffix in der Basis-Anzeige,
Einheiten-Sprung auf Stunden-Dezimalen bei sub-1h-Kollision) + 6 bereits
gruene Referenztests (u.a. $-Eskalation unveraendert). Nach Fix 9/9 gruen.
Sweep generative/tests -k dashboard: 318 passed, keine Regression.
#294-Nebenfund (Reviews 15.07.): _chart_scaling() (Chart 3 "Scaling" +
Legende 3) kopierte r["label"] unveraendert aus den all_log_runs-Zeilen.
Im primaeren Log-Pfad (_read_all_log_runs) ist das
`_PDF_LABELS.get(key, key)` -- fuer PDFs ausserhalb der 3 registrierten
_PDF_LABELS-Eintraege (Regelfall) faellt das auf den rohen, klein-
geschriebenen Log-Key zurueck (z. B. "cobaltite-paper" statt "Cobaltite
Paper"). Dieselbe Bugklasse traf bereits Chart 2 (Trade-off,
_chart_longitudinal) und wurde dort per U4-Fix lokal geloest
(_PDF_LABELS.get(key) or re.sub(...).title()) -- ch3/leg3 bekam den Fix
bisher nicht.

Fix: dieselbe Formel in _chart_scaling, aber nur wenn label==key (der
Fallback tatsaechlich griff) -- ein bereits besserer Label-Wert aus dem
DB-Fallback-Pfad (pdf_label/pdf_source-Ableitung in
eval_dashboard_server.py) bleibt unangetastet. Quelle bewusst NICHT
zentral in _read_all_log_runs geaendert, um den Blast-Radius auf andere
Konsumenten (_chart_acceptance u. a., nicht Teil dieses Fundes) nicht
auszuweiten.

Nachweis (TDD): RED zeigte den rohen Key in allen 3 Faellen (unregistrierte
PDF, Multi-Separator-Key) vor dem Fix, GREEN danach (4/4) -- inkl.
Regressionswaechter fuer den DB-Fallback-Fall (bereits guter Label bleibt
erhalten). Sweep generative/tests -k dashboard: 322 passed, keine
Regression.
Reviews 15.07.: Modell-Dropdown-Optionen zeigen jetzt die Anzahl VALIDER
Eval-Zeilen (hallucination_rate >= 0) je Modell -- Gemini-Fehllesungs-
Schutz: dort tragen Zeilen den bestehenden -1.0-Sentinel fuer "ungueltig"
(vgl. _chart_scatter/_matrix_cell_stats); n_valid zeigt fuer so ein Modell
korrekt 0, nicht die volle aber wertlose Zeilenzahl.

note_evals traegt kein eigenes "model"-Feld (das lebt in pipeline_runs) --
Join ueber run_id -> token_runs.model (bereits angereichert), derselbe
Mechanismus wie der bestehende Modell-Einzelwert-Filter. Basis
_matrix_base_rows (eval_version-skopiert, VOR pipeline_version/language/
pdf/run-Filtern) statt quality_rows -- dieselbe "Dropdown-Optionen vor
allen Filtern"-Konvention wie die Options-Liste selbst (Bestands-
Kommentar direkt darueber), sonst wuerde z. B. ein aktiver PDF-Filter die
angezeigten Zaehler unerwartet mitverschieben.

all_models wechselt von list[str] auf list[{model, n_valid}] (kein
bestehender Test/Konsument referenzierte die alte Form direkt). Client:
_initGlobalModelFilter zeigt "<Label> (n=X)", Dropdown-value bleibt der
Modellname (_rebuildFilterSelect-Signatur auf .model umgestellt,
Bestandsverhalten fuer die Auswahl-Persistenz ueber Auto-Refresh
unveraendert).

Nachweis (TDD): RED zeigte TypeError (all_models noch string[]) an allen
4 Stellen. Nach Fix 4/4 gruen -- inkl. Sentinel-Ausschluss, None-Werte
nicht als valide gezaehlt, Denylist-Regression, HTML-Anker fuer die
JS-Anzeige. Sweep generative/tests -k dashboard: 326 passed, keine
Regression.
…abel statt Filterbar

Design-Iteration nach Till-Feedback (Sichtprüfung vor der ersten Playwright-
Runde): Die urspruengliche Umsetzung von Punkt 1 haengte das eval_version-
Select als eigenes Filterbar-Element an — brach deren Einzeilen-Layout
bereits bei 1440px (6. Filter-Group + "weitere Versionen verfuegbar"-Badge
passten nicht mehr in eine Zeile, Screenshot-Beleg vor dieser Korrektur).

Filterbar-Variante verworfen zugunsten eines Inline-Selects am bestehenden,
dezenten eval_version-Meta-Label in der Sidebar (#sf-ev-select, ersetzt den
reinen Text-Pill #sf-ev-pill): gleiche Typografie/Farbe wie zuvor (Mono,
amber-Pill-Optik unveraendert), appearance:none + eigener kleiner Chevron
statt Systemstil, Optionen weiterhin "4.3 (n=27)" etc. Funktion unveraendert
(Param in allen Fetches inkl. Auto-Refresh, Klick-Pfad-Fix aus der vorigen
Runde bleibt erhalten: Select-Wert-Sync laeuft unabhaengig vom teuren
Innerhtml-Rebuild). #pm-evalver (Matrix-Sektion) bleibt Read-only-Text und
folgt der Auswahl unveraendert -- SSoT, beide lesen d.eval_version aus
derselben Antwort.

Die separate "weitere Versionen verfuegbar"-Warnbadge entfaellt: das Select
selbst (sichtbare Optionen beim Oeffnen) ist bereits die Affordanz, dass es
mehrere Versionen gibt -- kein zusaetzliches Element noetig, passt zum
Ziel "so reduziert wie moeglich".

Verifiziert (Playwright, isolierter Testserver, Live-Daten read-only):
Filterbar wieder einzeilig bei 1440px UND 1100px, hell + dunkel (Screenshot-
Vergleich vor/nach) -- Select fuegt sich unauffaellig neben "eval"-Label und
"auto-refresh 15 s" ein, kein klobiges Systemelement in keinem der beiden
Themes. 0 pageerrors.

Nachweis: HTML-Anker-Tests aktualisiert (kein Filterbar-Element mehr,
#sf-ev-select in der Sidebar, Fetch/Boot-Seed/Dropdown-Wiring-Tests
unveraendert gueltig), Node-Ausfuehrungstest fuer den Select-Wert-Sync-Fund
auf das neue Element umgestellt. generative/tests -k dashboard weiterhin
gruen (separat vor dem naechsten Commit erneut voll durchlaufen).
…9 Filterbar-Umbruch)

Reviews 15.07.:

U8: kpis-perf (5 Kacheln) brach bei <=1200px auf 4 Spalten bzw. bei
<=900px auf 2 Spalten -- 5 ist durch keine der beiden Spaltenzahlen teilbar
(5%4=1, 5%2=1), die letzte Kachel stand dadurch allein in der letzten Zeile
mit leeren Spalten daneben (Playwright-Diagnose 1100px: Kachel 4 bei
x=249..455 statt bis zum rechten Rand, 3 Spalten sichtbar leer). Fix:
letzte Kachel spannt per grid-column:1/-1 die volle Breite, wenn sie allein
in ihrer Zeile steht -- an beiden Breakpoints.

U9: das Modell-Filter brach als letzte Filter-Group allein in eine neue
Zeile. Kompaktere Gruppierung durch engeres .filter-group-Padding (14px auf
10px je Seite) und schmalere .fselect-Maximalbreite (200px auf 150px) bei
<=1200px -- 3 der 5 Groups stiessen vorher an die 200px-Kappung und
ueberschritten die verfuegbare Breite (~880px) um ~370px. Bei 1100px bleibt
"Modell" trotzdem allein in Zeile 2 (die reine Inhaltsbreite der uebrigen 4
Groups reicht nicht fuer alle 5 in eine Zeile, ohne PDF-/Lauf-Namen bei
echten Filterwerten unlesbar zu stauchen) -- aber jetzt kompakt statt mit
grossem Leerraum, kein Bruch mehr.

Verifiziert (Playwright, isolierter Testserver, Live-Daten read-only,
1100px, hell+dunkel): KPI-Zeile ohne Luecke, Filterbar-Umbruch kompakt,
0 pageerrors.

Nachweis: HTML-Anker-Tests fuer beide Breakpoint-Bloecke (grid-column-Span
an beiden Media-Queries, Padding-/Max-Width-Kompaktierung). Sweep
generative/tests -k dashboard: 330 passed, keine Regression.
…d-Ansicht

S2-C3 (Multi-Perspektiven-Dashboard-Review 2026-07-15): note_evals enthaelt
nur AKZEPTIERTE Notes (Selektions-Bias) -- sinkt die Akzeptanzquote
gleichzeitig mit einer Fehlerquoten-Aenderung, ist Letztere teils
Stichproben-Verschiebung statt reiner Qualitaetseffekt, ohne dass das im
bestehenden Fehlerquote-Spark-Chart sichtbar waere.

Fix: Akzeptanzquote als zweite, gestrichelte/gedaempfte Serie im
BESTEHENDEN Fehlerquote-Spark-Chart (Klick auf die Fehlerquote-KPI-Kachel,
_trendChart) -- kein neues Chart gebaut. _trendChart bekommt zwei optionale
Parameter (points2, label2); Y-Achse deckt beide Serien gemeinsam ab (nur
bei gleicher Einheit %, sonst ergibt eine gemeinsame Skala keinen Sinn --
z. B. die Dauer-Kachel bleibt unveraendert). Ohne points2 (alle anderen
KPI-Kacheln) ist das Verhalten exakt wie zuvor.

Zweite Serie bewusst schlank: eigene kleinere Punkte, aber OHNE Hover-
Tooltip-Kaesten (Zweck ist der visuelle Trend-Vergleich, kein zweiter
voller Interaktionslayer) -- kleine SVG-Legende (gestrichelte Linie +
Label) oben links macht die zweite Serie ohne Rueckgriff auf die Kachel-
Farbtoene erkennbar.

Nachweis: Node-Ausfuehrungstest der echten _trendChart-Funktion (kein
Nachbau) -- Overlay rendert bei uebergebenem points2, faellt bei fehlendem
points2/nicht-%-Einheit/Laengen-Mismatch sauber weg (Regressionswaechter
fuer alle anderen Kacheln), gemeinsame Skala deckt Werte ausserhalb des
Fehlerquote-Bereichs ab. HTML-Anker fuer die Aufrufer-Verdrahtung (nur
Fehlerquote-Kachel). Playwright-Sichtpruefung (eval_version 4.1, 15
Pipeline-Versionen, hell+dunkel): zwei klar unterscheidbare Linien (durch-
gezogen amber + gestrichelt gedaempft) auf gemeinsamer 0-100%-Achse, Legende
sichtbar, 0 pageerrors. Sweep generative/tests -k dashboard: 336 passed.
Nachbesserung Punkt 1 (4 externe Reviews nach PR #312, UX konvergent,
Beleg a04-matrix-41-light-scroll400.png): der pauschale
`.table-wrap.pm-scroll table.cmp thead th`-Reset (Punkt 0) setzte auch
th:first-child ("Quell-PDF") auf position:static zurueck. Beim
Horizontal-Scroll scrollte die Kopfzelle dadurch mit den Versions-Headern
weg, waehrend die per td:first-child gepinnte PDF-Namen-Spalte im tbody
stehen blieb -- der jeweils linkeste sichtbare Versions-Header landete
optisch ueber der gepinnten Spalte.

Fix: Reset gilt nur noch fuer thead th:not(:first-child); th:first-child
bekommt eine eigene Sticky-Left-Regel (top:auto hebt das top:49px der
>=1201px-Media-Query explizit auf, z-index 3 haelt sie ueber den
statischen Versions-Headern und der generischen th:first-child/
td:first-child-Regel).

Verifiziert per Playwright (playwright-core + firefox-1532, Testserver
8069 mit read-only kopierten Live-Daten, eval_version 4.1/15 Versionen):
1440px und 1100px, hell+dunkel, "Quell-PDF" bleibt beim Scroll sauber
gepinnt, kein Versions-Header ueberlappt, 0 pageerrors.
Nachbesserung Punkt 5 (trivial): die Cap-Fussnote "N weitere Version(en)
... nicht gezeigt" war keine echte Singular/Plural-Behandlung -- bei
genau 1 uebrig gebliebenen Version stand weiterhin die Klammer "(en)"
da. Gleiche Technik wie die bereits gefixte Matrix-Insight-Zeile ("1
Version" statt "1 Versionen").

Verifiziert live (eval_version 4.1, 35 gedroppte Versionen): Text zeigt
jetzt "35 weitere Versionen" statt "35 weitere Version(en)".
Nachbesserung Punkt 2 (UX-Review 16.07, Beleg
c-drift-v0.3.140-vs-v0.3.60-1100-light.png): der table-wrap-Wrapper der
Versions-Vergleichstabelle (Paarvergleich-Sektion) hatte keine
pm-scroll-Klasse -- bei 1100px war die letzte Spalte "Paarung" hart
abgeschnitten, bei 1280px blieben nur 2px Restluft (fragil, gleiche
Bugklasse wie #305/Punkt 0 oberhalb 1201px). Zusaetzlich lebte der
laengere PDF-Key-Drift-Hinweis als Teil der zentrierten <td colspan>-
Zeile INNERHALB der Tabelle -- deren Breite wird von den 9 Header-th
bestimmt, unabhaengig vom (leeren) tbody-Inhalt, und ueberstieg die
sichtbare Wrapper-Breite bei 1100px: der Text wurde mitten im Wort
geclippt.

Fix: derselbe pm-scroll-Mechanismus wie an der Matrix (haelt den
Scroll-Container in allen Viewport-Breiten aktiv, pinnt zugleich die
"Quell-PDF"-Spalte links). Der laengere Drift-Hinweis wandert in die
bestehende `summary`-Zeile ausserhalb des Scroll-Containers (Section-
statt Tabellenbreite als Umbruchgrenze); die In-Table-Leerzeile bleibt
kurz genug, um nie clippen zu muessen.

Verifiziert per Playwright (1100/1280/1440px, hell+dunkel): "Paarung"
nach Scroll vollstaendig sichtbar, Drift-Hinweis komplett lesbar ohne
Clipping, 0 pageerrors.
Nachbesserung Punkt 4 (Statistiker-Empfehlung, Reviews 15./16.07 zu PR
#312): unter eval_version 4.3 haengen alle 27 note_evals-Zeilen an
pipeline_version v0.3.144 -- das ist der Code-Stand des Re-Eval-Sweeps
(reeval_baseline.py), NICHT die Erzeugungsversion der Notes. Der
zugehoerige Lauf in pipeline_runs traegt dafuer den expliziten Marker
pipeline_version="reeval" (verifiziert im Quellcode), verknuepft ueber
run_id. Ohne Kennzeichnung liest die Matrix "v0.3.144" faelschlich als
Erzeugungsversion.

Fix: neue reine Funktion D._is_reeval_series(quality_rows, pipeline_runs)
prueft, ob JEDE Zeile der aktiven eval_version (_matrix_base_rows, vor
Einzelwert-Filtern) an einen pipeline_runs-Eintrag mit
pipeline_version=="reeval" haengt. Server exponiert das Ergebnis als
is_reeval_series im Payload; die Matrix-Insight-Sektion zeigt bei True
die Zeile "Re-Eval-Serie: Werte messen den Eval-Stand, nicht die
Erzeugungsversion der Notes."

RED->GREEN: Server-/HTML-Anker-Tests liefen zuerst gegen den unveraen-
derten Server (5 fehlgeschlagen, KeyError/AssertionError), nach Server-
und HTML-Verdrahtung 10/10 gruen. Verifiziert live gegen die kopierten
Produktionsdaten: eval_version 4.3 (realer #232-Fall) zeigt
is_reeval_series=True + sichtbaren Hinweis, eval_version 4.1 (normale
Generierungs-Runs) korrekt False/kein Hinweis. Playwright hell+dunkel,
0 pageerrors.
Nachbesserung Punkt 3 (Statistiker MEDIUM + UX-P3, Reviews 15./16.07 zu
PR #312): die gemeinsame 0-100%-Achse des Overlays stauchte die
Fehlerquoten-Serie auf ~1/3 der Chart-Hoehe (vorher hall-adaptiv), und
100%-Accept-Punkte aus n=1-3-Laeufen waren optisch nicht von
belastbaren unterscheidbar; der Punkt-Tooltip zeigte nur die
Fehlerquote, die Legende nur die Zweitserie.

Fix (4 Teile):
(a) Overlay zuschaltbar, Default AUS -- kleiner Toggle am Chart-Kopf
    (nur Fehlerquote-Kachel), Zustand in localStorage (analog
    theme-mode). Aus bleibt die Achse hall-adaptiv wie vor dem PR.
(b) Punkt-Tooltip zeigt bei aktivem Overlay beide Werte ("12,0% /
    Akzeptanz 72,2%").
(c) Legende benennt BEIDE Serien (vorher nur die Zweitserie) + Caveat-
    Zeile erklaert die unterschiedliche Basis (geroutete/generierte vs.
    evaluierte Notes).
(d) Accept-Punkte mit accept_n<20 (Payload-Feld aus Punkt 4/D3, bereits
    vorhanden) werden gedimmt statt gleichwertig zu belastbaren Werten
    dargestellt.

Verifiziert per Playwright gegen echte kopierte Produktionsdaten
(eval_version 4.1, 50 Versionen): Default aus (keine Dashed-Linie,
Achse 0-37,4%), nach Zuschalten Achse 0-100% + beide Serien in der
Legende + Caveat-Text + 4/48 Punkte volltonig (accept_n>=20) Rest
gedimmt (0.35 Opacity) + kombinierter Tooltip, Toggle-Zustand bleibt
nach Reload erhalten. Hell+dunkel, 0 pageerrors.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants