fix(web): Dunkelvariante an die .dark-Klasse binden — 100+ dark:-Angaben waren wirkungslos

Beim Einbau des Logos fiel auf, dass dessen Plattenkontur dark:stroke-white/25 im
dunklen Modus nicht griff. Die Ursache reicht weit ueber das Logo hinaus.

globals.css definierte die Farbtokens unter .dark, deklarierte aber kein
@custom-variant dark. In Tailwind 4 haengt die dark:-Utility-Variante per Vorgabe an
prefers-color-scheme, also an der Einstellung des Betriebssystems. Die Anwendung
schaltet den Modus jedoch ueber next-themes mit attribute=class. Beides lief damit
auseinander: sobald ein Nutzer im Portal auf dunkel stellte, waehrend sein System hell
stand, blieb JEDE dark:-Utility im Quellcode wirkungslos — ueber 100 Vorkommen in mehr
als zehn Dateien, darunter Status-, Warn- und Fehlerfarben, Hinweisboxen und Badges in
der Administration.

Unentdeckt geblieben ist es, weil Hintergrund und Textfarbe NICHT ueber dark: laufen,
sondern ueber die CSS-Variablen unter .dark. Die Oberflaeche wurde also grundsaetzlich
dunkel und nur die Feinheiten fehlten.

Am laufenden System in beide Richtungen gemessen. Vorher, bei html.dark und hellem
System: dark:bg-gray-800 ergab transparent, dark:text-green-400 blieb ohne Wirkung.
Nachher greifen beide korrekt, und im hellen Modus greifen sie weiterhin nicht — die
Logo-Kontur ist dort unsichtbar, im dunklen Modus weiss mit 25 Prozent Deckkraft.
221/221 Web-Tests gruen, Typpruefung sauber.

Erfasst als WINDOWS.md #13 und mit diesem Commit geschlossen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
This commit is contained in:
2026-09-07 11:35:49 +02:00
parent 83e8cedcd4
commit f8d7da073b
2 changed files with 37 additions and 3 deletions
+16 -3
View File
@@ -2,9 +2,9 @@
schema_version: 1 schema_version: 1
open_count: 3 open_count: 3
waived_count: 0 waived_count: 0
fixed_count: 9 fixed_count: 10
total_count: 12 total_count: 13
last_updated: 2026-09-07T08:42:34.794Z last_updated: 2026-09-07T09:35:49.019Z
--- ---
# Broken Windows Ledger # Broken Windows Ledger
@@ -27,6 +27,7 @@ last_updated: 2026-09-07T08:42:34.794Z
| 10 | 15 | unmet-truth | apps/web/src/app/(portal)/modules/tender-radar/page.tsx | | PERM-04 greift nicht auf modul-eigenen Routen: der Zugriffs-Guard sitzt nur in modules/[category]/[moduleSlug]/page.tsx. Die vier fest verdrahteten Modulrouten (tender-radar, dkv-fleet, cert-manager, domaincheck) samt Unterseiten (z.B. tender-radar/my-sources) laufen daran vorbei und rendern fuer einen USER OHNE Modulfreigabe vollstaendig, inklusive bedienbarer Knoepfe. Im Browser gemessen am 2026-09-07 mit nutzer2 (keine Freigabe): /modules/procurement/tender-radar zeigt korrekt die 403-Seite, /modules/tender-radar und /modules/tender-radar/my-sources und /modules/dkv-fleet zeigen die volle Seite. Keine Datenpreisgabe - die API antwortet auf allen Endpunkten mit 403 - aber der Nutzer sieht Bedienelemente, die er nicht benutzen darf, und bekommt rohe englische Techniktexte statt einer verstaendlichen Meldung. Beleg: .planning/phases/17-eigene-ausschreibungs-quellen-je-nutzer/uat-2026-09-07/befund-modul-eigene-route-ohne-freigabe.png | fixed | | 2026-09-07T08:03:45.978Z | 2026-09-07T08:40:04.951Z | | 10 | 15 | unmet-truth | apps/web/src/app/(portal)/modules/tender-radar/page.tsx | | PERM-04 greift nicht auf modul-eigenen Routen: der Zugriffs-Guard sitzt nur in modules/[category]/[moduleSlug]/page.tsx. Die vier fest verdrahteten Modulrouten (tender-radar, dkv-fleet, cert-manager, domaincheck) samt Unterseiten (z.B. tender-radar/my-sources) laufen daran vorbei und rendern fuer einen USER OHNE Modulfreigabe vollstaendig, inklusive bedienbarer Knoepfe. Im Browser gemessen am 2026-09-07 mit nutzer2 (keine Freigabe): /modules/procurement/tender-radar zeigt korrekt die 403-Seite, /modules/tender-radar und /modules/tender-radar/my-sources und /modules/dkv-fleet zeigen die volle Seite. Keine Datenpreisgabe - die API antwortet auf allen Endpunkten mit 403 - aber der Nutzer sieht Bedienelemente, die er nicht benutzen darf, und bekommt rohe englische Techniktexte statt einer verstaendlichen Meldung. Beleg: .planning/phases/17-eigene-ausschreibungs-quellen-je-nutzer/uat-2026-09-07/befund-modul-eigene-route-ohne-freigabe.png | fixed | | 2026-09-07T08:03:45.978Z | 2026-09-07T08:40:04.951Z |
| 11 | 13 | unmet-truth | apps/api/src/tenders/tender-fingerprint.ts | | Cross-Source-Dedup kollabiert die Mehrzahl echter Duplikate nicht: tenderFingerprint() haengt [buyerName, title, cpvDivisionKey, deadlineKey, valueBucket] zu einem String und hasht ihn. Die Scraper-Adapter liefern cpvDivisions immer leer, DOE dagegen meist gefuellt - dieselbe Ausschreibung ergibt aus beiden Quellen verschiedene Fingerabdruecke, die Fingerprint-Stufe greift also gerade dort nicht, wo sie greifen soll. Steht als offener Rest in 13-VERIFICATION.md (status gaps_found, 3/4 Wahrheiten), war aber bisher nicht im Ledger und damit unsichtbar. Die frueher vermutete Ursache (Normalizer-Defaults) ist mit 9881005 geschlossen und nicht gemeint. | fixed | | 2026-09-07T08:09:04.188Z | 2026-09-07T08:42:34.794Z | | 11 | 13 | unmet-truth | apps/api/src/tenders/tender-fingerprint.ts | | Cross-Source-Dedup kollabiert die Mehrzahl echter Duplikate nicht: tenderFingerprint() haengt [buyerName, title, cpvDivisionKey, deadlineKey, valueBucket] zu einem String und hasht ihn. Die Scraper-Adapter liefern cpvDivisions immer leer, DOE dagegen meist gefuellt - dieselbe Ausschreibung ergibt aus beiden Quellen verschiedene Fingerabdruecke, die Fingerprint-Stufe greift also gerade dort nicht, wo sie greifen soll. Steht als offener Rest in 13-VERIFICATION.md (status gaps_found, 3/4 Wahrheiten), war aber bisher nicht im Ledger und damit unsichtbar. Die frueher vermutete Ursache (Normalizer-Defaults) ist mit 9881005 geschlossen und nicht gemeint. | fixed | | 2026-09-07T08:09:04.188Z | 2026-09-07T08:42:34.794Z |
| 12 | 14 | unrun-verify | .planning/phases/14-rss-email-alert-ingestion-module-rollout/14-03-PLAN.md | | 14-03 Task 4 (checkpoint human-verify, gate blocking): echtes Portal-Alert-Postfach ueber den Exchange/EWS-Weg anbinden und eine echte Alarm-Mail ingestieren. Zu belegen sind (a) die Mail erzeugt eine mandantenprivate Ausschreibung, (b) ein anderer Mandant sieht sie nicht, (c) der EWS-Body-Abruf liefert nicht-leeren, nicht-verstuemmelten Inhalt gegen einen echten Exchange-Server. Der handgeschriebene NTLM/SOAP-Weg ist nur gegen gemocktes httpntlm.post geprueft, nie gegen einen echten Server. Braucht ein echtes Postfach - vom User am 2026-07-24 bewusst zurueckgestellt. Stand bisher nur in 14-VERIFICATION.md (status human_needed), nicht im Ledger. | open | | 2026-09-07T08:09:04.406Z | | | 12 | 14 | unrun-verify | .planning/phases/14-rss-email-alert-ingestion-module-rollout/14-03-PLAN.md | | 14-03 Task 4 (checkpoint human-verify, gate blocking): echtes Portal-Alert-Postfach ueber den Exchange/EWS-Weg anbinden und eine echte Alarm-Mail ingestieren. Zu belegen sind (a) die Mail erzeugt eine mandantenprivate Ausschreibung, (b) ein anderer Mandant sieht sie nicht, (c) der EWS-Body-Abruf liefert nicht-leeren, nicht-verstuemmelten Inhalt gegen einen echten Exchange-Server. Der handgeschriebene NTLM/SOAP-Weg ist nur gegen gemocktes httpntlm.post geprueft, nie gegen einen echten Server. Braucht ein echtes Postfach - vom User am 2026-07-24 bewusst zurueckgestellt. Stand bisher nur in 14-VERIFICATION.md (status human_needed), nicht im Ledger. | open | | 2026-09-07T08:09:04.406Z | |
| 13 | 1 | unmet-truth | apps/web/src/app/globals.css | | Tailwind-4-Dunkelvariante war nie an die .dark-Klasse gebunden: globals.css definierte die Farbtokens unter .dark, deklarierte aber kein @custom-variant dark. In Tailwind 4 haengt die dark:-Utility-Variante per Vorgabe an prefers-color-scheme, nicht an einer Klasse. Die App schaltet den Modus jedoch ueber next-themes mit attribute=class. Folge: JEDE dark:-Utility im Web-Quellcode (ueber 100 Vorkommen in 10+ Dateien - Status-, Warn- und Fehlerfarben, Hinweisboxen, Badges in der Administration) blieb wirkungslos, sobald der Nutzer im Portal auf dunkel stellte, waehrend sein Betriebssystem hell stand. Am laufenden System gemessen: html.dark gesetzt, prefers-color-scheme hell, dark:bg-gray-800 ergab transparent und dark:text-green-400 blieb ohne Wirkung. Unentdeckt geblieben, weil Hintergrund und Textfarbe ueber CSS-Variablen unter .dark laufen und deshalb korrekt umschalten. Aufgedeckt beim Logo-Einbau 260907-fgv, dessen Plattenkontur dark:stroke-white/25 aus demselben Grund nicht griff. | fixed | | 2026-09-07T09:32:24.960Z | 2026-09-07T09:35:49.019Z |
````json ````json
[ [
@@ -173,6 +174,18 @@ last_updated: 2026-09-07T08:42:34.794Z
"reason": "", "reason": "",
"recorded_at": "2026-09-07T08:09:04.406Z", "recorded_at": "2026-09-07T08:09:04.406Z",
"resolved_at": null "resolved_at": null
},
{
"id": 13,
"kind": "unmet-truth",
"phase": "1",
"file": "apps/web/src/app/globals.css",
"line": null,
"description": "Tailwind-4-Dunkelvariante war nie an die .dark-Klasse gebunden: globals.css definierte die Farbtokens unter .dark, deklarierte aber kein @custom-variant dark. In Tailwind 4 haengt die dark:-Utility-Variante per Vorgabe an prefers-color-scheme, nicht an einer Klasse. Die App schaltet den Modus jedoch ueber next-themes mit attribute=class. Folge: JEDE dark:-Utility im Web-Quellcode (ueber 100 Vorkommen in 10+ Dateien - Status-, Warn- und Fehlerfarben, Hinweisboxen, Badges in der Administration) blieb wirkungslos, sobald der Nutzer im Portal auf dunkel stellte, waehrend sein Betriebssystem hell stand. Am laufenden System gemessen: html.dark gesetzt, prefers-color-scheme hell, dark:bg-gray-800 ergab transparent und dark:text-green-400 blieb ohne Wirkung. Unentdeckt geblieben, weil Hintergrund und Textfarbe ueber CSS-Variablen unter .dark laufen und deshalb korrekt umschalten. Aufgedeckt beim Logo-Einbau 260907-fgv, dessen Plattenkontur dark:stroke-white/25 aus demselben Grund nicht griff.",
"status": "fixed",
"reason": "",
"recorded_at": "2026-09-07T09:32:24.960Z",
"resolved_at": "2026-09-07T09:35:49.019Z"
} }
] ]
```` ````
+21
View File
@@ -1,5 +1,26 @@
@import "tailwindcss"; @import "tailwindcss";
/*
* Bindet die `dark:`-Utility-Variante an die `.dark`-Klasse.
*
* Tailwind 4 haengt `dark:` per Vorgabe an `prefers-color-scheme`, also an die
* Einstellung des Betriebssystems. Diese Anwendung schaltet den Modus aber ueber
* next-themes mit `attribute="class"` (siehe app/layout.tsx) — der Nutzer waehlt
* hell/dunkel im Portal, unabhaengig von seinem System.
*
* Ohne diese Zeile klaffte beides auseinander: die Farbtokens unter `.dark` weiter
* unten schalteten korrekt um, jede einzelne `dark:`-Utility im Quellcode blieb
* dagegen wirkungslos, sobald System und Portal-Einstellung nicht zufaellig
* uebereinstimmten. Betroffen waren ueber 100 Stellen — Status-, Warn- und
* Fehlerfarben, Hinweisboxen und Badges in der Administration. Der Fehler fiel
* lange nicht auf, weil Hintergrund und Textfarbe eben NICHT ueber `dark:` laufen,
* die Oberflaeche also grundsaetzlich dunkel wurde und nur die Feinheiten fehlten.
*
* Aufgedeckt beim Logo-Einbau (Quick 260907-fgv, WINDOWS.md #13), dessen
* Plattenkontur `dark:stroke-white/25` aus genau diesem Grund nicht griff.
*/
@custom-variant dark (&:where(.dark, .dark *));
@theme inline { @theme inline {
--color-primary: var(--primary); --color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground); --color-primary-foreground: var(--primary-foreground);