From 0875ae2688379e5994c489693ac3391ec35cbf10 Mon Sep 17 00:00:00 2001 From: DeeCee03 Date: Sat, 29 Aug 2026 17:53:51 +0200 Subject: [PATCH] =?UTF-8?q?feat(carbon):=20Carbon-Konformit=C3=A4t=20nachp?= =?UTF-8?q?r=C3=BCfbar=20machen=20statt=20behaupten?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Recherche an der Quelle statt an der Dokumentation. Zwei Ergebnisse: 1. Carbon regelt Datenvisualisierung nur teilweise. Farben, Gitter, Achsen, Tooltip und Tickanzahl sind verbindlich festgelegt — Paddings, Margins und Dashboard-Raster dagegen NICHT. Die Carbon-Seiten zu Chart-Anatomie und Dashboards tragen ausdrücklich den Hinweis "this guidance is a work in progress" und enthalten keine Zahlenwerte. Unsere Abstandswerte stammen deshalb aus der Carbon-Spacing-Skala und der Planung mit dem Branchenpartner, nicht aus der Data-Viz-Norm. Das steht jetzt so in DESIGN-SYSTEM.md §0. 2. Eine echte Abweichung gefunden: Der Tooltip ist seit Carbon v11 hell (--cds-layer-02 #ffffff auf --cds-text-primary #161616, abgesetzt allein durch den Schatten). Unser Theme führte den dunklen v10-Stand (#393939) und berief sich dabei auf das ausgelieferte Stylesheet — die Angabe war veraltet. Korrigiert. Neu: - docs/carbon-referenz.json — kanonische Werte mit Quelle, Version und Prüfdatum, gezogen aus @carbon/charts 1.23.0 styles.min.css und configuration.ts. Bewusste Abweichungen (Balkenbreite 48 statt 16, feste Kategoriefarben statt n-Serien-Paletten) sind dort begründet. - scripts/verify-carbon.mjs + npm run verify:carbon — gleicht chartTheme.ts gegen die Referenz ab und bricht bei Drift ab. - docs/PRUEFFRAGEN.md — Prüffragen für die Gestaltungsabnahme mit nachprüfbarem Ja/Nein, plus Leseliste (FT Visual Vocabulary, IBCS, Cleveland & McGill, Few, Datawrapper Academy). Die 14er-Palette wurde gegen die CSS-Variablen --cds-charts-14-1-* geprüft und stimmt exakt; ebenso Gitter #e0e0e0, Achse #8d8d8d und Achsentext #525252. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01QPeZNwSeU7qG3RRCwaxEZX --- README.md | 16 +- docs/DESIGN-SYSTEM.md | 56 ++++++- docs/PRUEFFRAGEN.md | 108 ++++++++++++++ docs/carbon-referenz.json | 73 ++++++++++ .../components/charts/ChartTooltip.tsx | 27 ++-- features/dashboard/utils/chartTheme.ts | 12 +- package.json | 3 +- scripts/verify-carbon.mjs | 137 ++++++++++++++++++ 8 files changed, 412 insertions(+), 20 deletions(-) create mode 100644 docs/PRUEFFRAGEN.md create mode 100644 docs/carbon-referenz.json create mode 100644 scripts/verify-carbon.mjs diff --git a/README.md b/README.md index 9fbba66..a434662 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,9 @@ features/dashboard/ pages/ die sechs Seiten — reine Komponenten ({ vm }) utils/ chartTheme (Carbon-Tokens), formatters, dateUtils ui/ Subframe-Komponentenbibliothek (unverändert übernommen) -docs/ Design-System, KPI-Katalog, Navigation, Datenmodell, Herkunft, Zielsystem, Leitplanken +docs/ Design-System, KPI-Katalog, Navigation, Datenmodell, + Herkunft, Zielsystem, Leitplanken, Prüffragen +scripts/verify-carbon.mjs gleicht chartTheme.ts gegen die Carbon-Referenz ab ``` **Wichtigste Dateien für den Einbau:** `features/dashboard/data/calculations.ts` @@ -86,6 +88,18 @@ Attrappe ist und in welcher Reihenfolge der Einbau sinnvoll ist. Die Seite **/komponenten** zeigt jeden Baustein isoliert mit Dateipfad — der schnellste Weg, sich zu orientieren. +```bash +npm run verify:carbon # Palette, neutrale Werte, Tooltip, Achsenraster gegen IBM Carbon +``` + +Die Referenzwerte stehen mit Quelle und Version in +[`docs/carbon-referenz.json`](docs/carbon-referenz.json), gezogen aus dem +ausgelieferten Stylesheet von `@carbon/charts` — nicht aus der Dokumentation, +die an mehreren Stellen davon abweicht. + +Für die Gestaltungsabnahme: [`docs/PRUEFFRAGEN.md`](docs/PRUEFFRAGEN.md) — +Prüffragen mit nachprüfbarem Ja/Nein, für Reviews ohne Designerauge. + --- ## Zielsystem diff --git a/docs/DESIGN-SYSTEM.md b/docs/DESIGN-SYSTEM.md index 204a7a5..08e4c0d 100644 --- a/docs/DESIGN-SYSTEM.md +++ b/docs/DESIGN-SYSTEM.md @@ -4,10 +4,48 @@ Grundlage ist **IBM Carbon Data Visualization**, ergänzt um die Spacing- und Zahlenregeln aus der eigenen Planung (`UI_UX_GUIDELINES.md`, Januar 2026). Die konkreten Farb- und Linienwerte sind aus dem ausgelieferten Stylesheet von -`@carbon/charts` (`dist/styles.css`, Light Theme) übernommen — nicht aus der -Dokumentation abgeschrieben. Alles Folgende ist in +`@carbon/charts` (Light Theme) übernommen — nicht aus der Dokumentation +abgeschrieben. Alles Folgende ist in `features/dashboard/utils/chartTheme.ts` als Code hinterlegt. +## 0. Was Carbon regelt — und was nicht + +Wichtig für jeden, der hier etwas anzweifelt: **Carbon regelt Datenvisualisierung +nur teilweise.** Die Grenze verläuft so: + +| Frage | Regelt Carbon? | Wo steht die Antwort | +|---|---|---| +| Farbpalette, Reihenfolge, Statusfarben | **ja, verbindlich** | ausgeliefertes Stylesheet, CSS-Variablen `--cds-charts-14-1-*` | +| Gitter-, Achsen- und Textfarben | **ja** | dieselben Quellen | +| Tooltip-Aufbau | **ja** | `.cds--cc--tooltip` im Stylesheet | +| Tickanzahl je Achse | **ja** | `configuration.ts` (y: 5, x: 15) | +| Wann welcher Diagrammtyp | teilweise, qualitativ | Carbon „Simple/Complex charts" | +| **Padding, Margins, Kartenabstände** | **nein** | siehe unten | +| Dashboard-Raster, Kartenhöhen | **nein**, nur Prinzipien | siehe unten | + +Die Carbon-Seiten zu Chart-Anatomie und Dashboards tragen ausdrücklich den +Hinweis „this guidance is a work in progress" und enthalten **keine Zahlenwerte** +für Abstände. Wer dort nach Paddings sucht, findet Prinzipien („create a clear +visual hierarchy", „limit the number of metrics") — richtig, aber nicht +nachprüfbar. + +**Die Abstandswerte in diesem Dokument stammen deshalb nicht von Carbon**, +sondern aus zwei anderen Quellen: + +1. der **Carbon-Spacing-Skala und dem 2x-Grid** des allgemeinen Design-Systems + (4/8/12/16/24/32/48…) — dieselbe Skala, aus der auch Davids + `INTERIM-UI-DESIGN-STANDARDS.md` im Zielsystem schöpft; +2. der eigenen Planung mit dem Branchenpartner (`UI_UX_GUIDELINES.md`). + +Beides ist konsistent zueinander. Es ist aber ehrlicher zu sagen: Bei Farben und +Achsen folgen wir einer Norm, bei Abständen einer begründeten Hauskonvention. + +**Nachprüfbar gemacht:** `npm run verify:carbon` gleicht `chartTheme.ts` gegen +`docs/carbon-referenz.json` ab — Palette, neutrale Werte, Tooltip, Achsenraster. +Die Referenzdatei nennt Quelle, Version und Prüfdatum. Bewusste Abweichungen +(Balkenbreite, feste Kategoriefarben) sind dort begründet hinterlegt und werden +als Hinweis ausgegeben, nicht als Fehler. + --- ## 1. Farben @@ -116,13 +154,19 @@ wechselt mitten im Lesen die Einheit; die Nulllinie trägt gar keine Einheit. ## 3. Tooltip -Einheitlich für alle Charts (`components/charts/ChartTooltip.tsx`): +Einheitlich für alle Charts (`components/charts/ChartTooltip.tsx`), Werte aus +`.cds--cc--tooltip` im ausgelieferten Stylesheet: -- Fläche `#393939`, Text `#ffffff` -- Label 12 px mit 80 % Deckkraft, Wert 13 px fett in Tabellenziffern -- kein Rahmen, kein Doppelpunkt zwischen Name und Wert +- Fläche `#ffffff` (`--cds-layer-02`), Text `#161616` (`--cds-text-primary`) +- abgesetzt allein durch den Schatten `0 1px 6px 0 rgba(0,0,0,.2)` — **kein Rahmen** +- Label und Reihenname 12 px in `#525252`, Wert fett in Tabellenziffern +- kein Doppelpunkt zwischen Name und Wert, maximal 270 px breit - Farbmarker je Reihe, 10 × 10 px +**Achtung bei älteren Quellen:** Bis Carbon v10 war der Tooltip dunkel +(`#393939` auf weißem Text). Sehr viele Artikel und Screenshots zeigen noch +diesen Stand. Maßgeblich ist das ausgelieferte Stylesheet, nicht die Bildersuche. + Ein gemeinsamer Tooltip verhindert, dass jedes Chart eigene Regeln entwickelt. --- diff --git a/docs/PRUEFFRAGEN.md b/docs/PRUEFFRAGEN.md new file mode 100644 index 0000000..2379c1f --- /dev/null +++ b/docs/PRUEFFRAGEN.md @@ -0,0 +1,108 @@ +# Prüffragen für die Gestaltungsabnahme + +Für Reviews ohne Designerauge. Die Fragen sind so gestellt, dass man sie am +Bildschirm beantworten kann — jede hat ein nachprüfbares Ja oder Nein, keine +Geschmacksfrage. + +Reihenfolge nach Wirkung: Wer nur zehn Minuten hat, nimmt Abschnitt 1. + +--- + +## 1. Die vier Fragen, die die meisten Fehler finden + +**1. Nenne die Aussage der Seite in einem Satz.** +Geht das nicht, fehlt die Hierarchie. Auf jeder Seite muss genau ein Block +dominieren. Sehen alle Karten gleich aus, ist keiner wichtig. + +**2. Gibt es ein Diagramm, dessen Balken oder Segmente alle etwa gleich groß sind?** +Dann trägt es keine Information. Typische Ursachen: Werte in einem engen Band +(74–96 %) auf einer Skala ab null, oder eine Rangfolge aus 5/4/3/3/3. +Entweder die Kodierung wechseln oder die Karte streichen. + +**3. Steht dieselbe Zahl zweimal auf der Seite?** +Kennzahl oben und Fußzeile unter dem Diagramm sind der übliche Fall. Eine +Wiederholung ist kein Beleg, sondern verbrauchter Platz. + +**4. Was soll der Betrachter danach tun?** +Findet sich darauf keine Antwort, ist es ein Bericht, kein Dashboard. Eine +Handlungsliste braucht je Zeile einen Betrag und ein Verb. + +--- + +## 2. Diagramme + +| Frage | Wenn nein | +|---|---| +| Beginnt jede Balkenachse bei null? | Balken kodieren über Länge — abgeschnitten lügen sie. Bei engen Wertebändern Punktdiagramm statt Balken | +| Hat jede Achse runde Werte (0/50/100, nicht 0/55/110)? | Der Leser rechnet bei jedem Blick mit | +| Trägt eine Achse genau eine Einheit? | „0 €" neben „10 T€" ist ein Einheitenwechsel mitten in der Skala | +| Belegt in einem Ring kein Segment mehr als etwa 80 %? | Dann ist die Bezugsgröße falsch gewählt, nicht der Diagrammtyp | +| Hat der Ring höchstens fünf Segmente? | Alles darüber gehört zu „Sonstige" zusammengefasst | +| Zeigt jede Legende eine Reihe, die es im Bild wirklich gibt? | Tote Legendeneinträge sind ein häufiger und peinlicher Fehler | +| Sind Kategorie- und Ampelfarben getrennt? | Eine grüne Kategorie neben einem grünen Positiv-Status ist nicht mehr lesbar | +| Behält eine Kategorie ihre Farbe über alle Seiten? | Sonst muss jede Legende neu gelesen werden | +| Ist bei jeder Karte erkennbar, ob sie den Zeitfilter beachtet? | Stichtagswerte neben gefilterten Werten ohne Kennzeichnung erzeugen falsche Schlüsse | + +--- + +## 3. Zahlen und Text + +- Tausendertrennung überall gleich? (`1.900`, nie `1900`) +- Dieselbe Zahl im selben Format? (`177 T€` und `177.010 €` auf einer Seite ist ein Bruch) +- Singular und Plural korrekt? („1 Tag", nicht „1 Tage") +- Spaltenköpfe einheitlich geschrieben? +- Bricht irgendwo Text ab, der nicht abbrechen dürfte? (Beträge, Kartenuntertitel) + +--- + +## 4. Raster und Zustände + +- Erzeugt eine Karte tote Fläche, weil die Nachbarkarte höher ist? + Gleiche Höhe ist nur bei gleicher Struktur richtig (Kennzahlenreihe), nicht bei + Diagramm neben Tabelle. +- Was passiert, wenn die Filter alles wegschneiden? Jede Ansicht braucht einen + Leerzustand. +- Ist der Tastaturfokus sichtbar? Mit Tab durch die Seite gehen. +- Schließt jedes Menü mit Escape? +- Verspricht ein Hover-Effekt eine Interaktion, die es nicht gibt? + +--- + +## 5. Womit man das Auge trainiert + +Kurz und ergiebig, in dieser Reihenfolge: + +1. **FT Visual Vocabulary** — ein Poster, das Diagrammtypen nach Aussageabsicht + ordnet (Vergleich, Verteilung, Anteil, Veränderung …). + Das beste Verhältnis von Aufwand zu Ertrag überhaupt. + `github.com/Financial-Times/chart-doctor` → `visual-vocabulary` +2. **IBCS — International Business Communication Standards**, die SUCCESS-Regeln. + Das ist der ausgearbeitete Standard für *Geschäfts*grafik, den Carbon nicht + ersetzt: einheitliche Notation, Ist/Plan/Vorjahr, Informationsdichte. + Frei unter CC BY-SA auf `ibcs.com`. +3. **Cleveland & McGill (1984)**, „Graphical Perception". Die Studie, aus der die + Rangfolge der Kodierungen stammt: Position wird genauer gelesen als Länge, + Länge genauer als Winkel, Winkel genauer als Fläche. Erklärt in einer Seite, + warum Ringe schwach und Punktdiagramme stark sind. +4. **Stephen Few, „Information Dashboard Design"**. Das Standardwerk zur + Dashboard-Kritik; die ersten beiden Kapitel reichen für ein geschultes Auge. +5. **Datawrapper Academy** (`academy.datawrapper.de`) — kurze, gut belegte + Artikel zu einzelnen Fragen („Wann Balken, wann Linien", „Warum keine zwei + Y-Achsen"). + +Zum Zielsystem passend: + +6. **Carbon Data Visualization** (`carbondesignsystem.com/data-visualization/`) + für Farben, Achsen und Diagrammtypen — mit der Einschränkung aus + [DESIGN-SYSTEM.md](DESIGN-SYSTEM.md) Abschnitt 0: Abstände regelt Carbon nicht. +7. **Carbon Spacing und 2x-Grid** (`carbondesignsystem.com/elements/spacing/`) + für genau diese Abstände. + +--- + +## 6. Was hier bewusst nicht geprüft wird + +Layout und Anordnung sind im Zielsystem **Entscheidung des Designers** +(siehe [LEITPLANKEN.md](LEITPLANKEN.md)). Verbindlich zu übergeben sind +Kennzahl-Definitionen, Diagrammregeln und Verhalten. Wer diese Liste im Review +benutzt, prüft die Substanz — nicht, ob eine Karte 40 px weiter links sitzt. diff --git a/docs/carbon-referenz.json b/docs/carbon-referenz.json new file mode 100644 index 0000000..b7862a9 --- /dev/null +++ b/docs/carbon-referenz.json @@ -0,0 +1,73 @@ +{ + "$kommentar": [ + "Kanonische IBM-Carbon-Werte, gegen die dieses Dashboard geprüft wird.", + "Quelle ist NICHT die Dokumentation, sondern das ausgelieferte Stylesheet und die", + "Default-Konfiguration von @carbon/charts — Doku und Code weichen voneinander ab.", + "Geprüft von scripts/verify-carbon.mjs (npm run verify:carbon).", + "Beim Aktualisieren: Werte neu aus der Quelle ziehen, nicht abtippen." + ], + "quelle": { + "stylesheet": "https://cdn.jsdelivr.net/npm/@carbon/charts@1.23.0/styles.min.css", + "konfiguration": "https://github.com/carbon-design-system/carbon-charts/blob/master/packages/core/src/configuration.ts", + "geprueftAm": "2026-08-29", + "version": "@carbon/charts 1.23.0" + }, + + "kategorialePalette": { + "herkunft": "CSS-Variablen --cds-charts-14-1-1 bis --cds-charts-14-1-14", + "regel": "Reihenfolge ist verbindlich, Farben immer von vorne vergeben", + "werte": [ + "#6929c4", + "#1192e8", + "#005d5d", + "#9f1853", + "#fa4d56", + "#520408", + "#198038", + "#002d9c", + "#ee5396", + "#b28600", + "#009d9a", + "#012749", + "#8a3800", + "#a56eff" + ] + }, + + "neutraleWerte": { + "gitterlinie": { "wert": "#e0e0e0", "token": "--cds-layer-accent-01" }, + "achsenlinie": { "wert": "#8d8d8d", "token": "--cds-border-strong-01" }, + "achsenbeschriftung": { "wert": "#525252", "token": "--cds-text-secondary" }, + "textPrimaer": { "wert": "#161616", "token": "--cds-text-primary" } + }, + + "tooltip": { + "herkunft": ".cds--cc--tooltip und .content-box im Stylesheet", + "hintergrund": { "wert": "#ffffff", "token": "--cds-layer-02" }, + "text": { "wert": "#161616", "token": "--cds-text-primary" }, + "schatten": "0 1px 6px 0 rgba(0,0,0,.2)", + "schriftgroessePx": 12, + "maxBreitePx": 270, + "hinweis": "Bis Carbon v10 war der Tooltip dunkel (#393939). Seit v11 ist er hell." + }, + + "achsen": { + "herkunft": "configuration.ts", + "ticksYAchse": 5, + "ticksXAchse": 15 + }, + + "bewusstAbweichend": { + "$kommentar": "Werte, die wir kennen und absichtlich anders setzen. Der Prüfer meldet sie als Abweichung mit Begründung, nicht als Fehler.", + "balkenMaxBreitePx": { + "carbon": 16, + "hier": 48, + "grund": "Carbons 16 px sind auf dichte Mehrreihen-Charts ausgelegt. Unsere Balken zeigen vier bis sechs Kategorien in einer breiten Karte; 16 px wirken dort wie Striche. Die Regel 'Abstand ≥ halbe Balkenbreite' bleibt eingehalten." + }, + "festeKategorieFarben": { + "carbon": "n-Serien-Paletten (--cds-charts-2-*, -3-*, -4-*, -5-*) je nach Anzahl der Reihen", + "hier": "feste Farbe je Gewerk und je Status über alle Charts hinweg", + "grund": "Carbons n-Serien-Paletten optimieren den Kontrast innerhalb EINES Charts. Über mehrere Charts hinweg würde Heizung dadurch die Farbe wechseln. Wiedererkennbarkeit schlägt hier Binnenkontrast." + } + } +} diff --git a/features/dashboard/components/charts/ChartTooltip.tsx b/features/dashboard/components/charts/ChartTooltip.tsx index 31f5d44..679b2e8 100644 --- a/features/dashboard/components/charts/ChartTooltip.tsx +++ b/features/dashboard/components/charts/ChartTooltip.tsx @@ -1,14 +1,19 @@ "use client"; import React from "react"; -import { CHART } from "../../utils/chartTheme"; +import { CHART, TOOLTIP_SCHATTEN } from "../../utils/chartTheme"; /** * Einheitlicher Tooltip für alle Charts. * - * Carbon-Vorgabe: dunkle Fläche (#393939), heller Text, 12 px Label, - * kein Rahmen, kein Doppelpunkt zwischen Name und Wert. - * Ein gemeinsamer Tooltip verhindert, dass jedes Chart eigene Regeln erfindet. + * Carbon-Vorgabe (@carbon/charts, .cds--cc--tooltip): helle Fläche + * (--cds-layer-02), Text in --cds-text-primary, 12 px, abgesetzt allein durch + * den Schatten — kein Rahmen, kein Doppelpunkt zwischen Name und Wert, + * maximal 270 px breit. + * + * Bis Carbon v10 war der Tooltip dunkel (#393939). Viele Quellen im Netz + * zeigen noch diesen Stand; maßgeblich ist das ausgelieferte Stylesheet. + * Geprüft von scripts/verify-carbon.mjs. * * Die Props sind bewusst weit typisiert: Recharts reicht sein internes * Payload-Format durch, das je nach Chart-Typ leicht abweicht. @@ -49,25 +54,29 @@ export function ChartTooltip({ return (
{labelText ? ( -
+
{labelPrefix ? `${labelPrefix} ` : ""} {labelText}
) : null}
{reihen.map((reihe) => ( -
+
{reihe.color ? ( ) : null} - {reihe.name} + {reihe.name} {formatWert(reihe.value)}
))} diff --git a/features/dashboard/utils/chartTheme.ts b/features/dashboard/utils/chartTheme.ts index 5c5a226..c70a796 100644 --- a/features/dashboard/utils/chartTheme.ts +++ b/features/dashboard/utils/chartTheme.ts @@ -7,7 +7,7 @@ * Achse #8d8d8d (--cds-border-strong-01) * Text primär #161616 (--cds-text-primary) * Text sek. #525252 (--cds-text-secondary) - * Tooltip #393939 (--cds-background-inverse) auf #ffffff Text + * Tooltip #ffffff (--cds-layer-02) auf #161616 Text, Schatten statt Rahmen * * Warum kein bunter "Marketing"-Farbsatz: Die Carbon-Palette ist auf * Unterscheidbarkeit und Kontrast optimiert und in dieser Reihenfolge @@ -148,10 +148,16 @@ export const CHART = { achse: "#8d8d8d", text: "#161616", textSekundaer: "#525252", - tooltipBg: "#393939", - tooltipFg: "#ffffff", + // Seit Carbon v11 ist der Tooltip hell: --cds-layer-02 auf --cds-text-primary, + // getrennt vom Untergrund allein durch den Schatten. Bis v10 war er dunkel + // (#393939) — diese Angabe steht in vielen älteren Quellen noch so. + tooltipBg: "#ffffff", + tooltipFg: "#161616", } as const; +/** Tooltip-Schatten laut @carbon/charts (.cds--cc--tooltip). */ +export const TOOLTIP_SCHATTEN = "0 1px 6px 0 rgba(0,0,0,.2)"; + /** * Chart-Innenabstände. * Carbon rechnet Achsenbeschriftungen in den Chart-Rahmen ein; die Karte selbst diff --git a/package.json b/package.json index ee7c366..5ad853f 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "dev": "next dev -p 4300", "build": "next build", "start": "next start -p 4300", - "lint": "eslint" + "lint": "eslint", + "verify:carbon": "node scripts/verify-carbon.mjs" }, "dependencies": { "@subframe/core": "^1.155.0", diff --git a/scripts/verify-carbon.mjs b/scripts/verify-carbon.mjs new file mode 100644 index 0000000..fbddd46 --- /dev/null +++ b/scripts/verify-carbon.mjs @@ -0,0 +1,137 @@ +#!/usr/bin/env node +/** + * Prüft chartTheme.ts gegen docs/carbon-referenz.json. + * + * Warum das existiert: „Wir halten uns an IBM Carbon" ist eine Behauptung, + * solange sie niemand nachrechnet. Farbwerte driften beim Kopieren, und die + * Carbon-Dokumentation widerspricht stellenweise dem ausgelieferten Code. + * Dieser Prüfer macht aus der Behauptung eine Zusage. + * + * Aufruf: npm run verify:carbon + */ + +import { readFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import { dirname, join } from "node:path"; + +const wurzel = join(dirname(fileURLToPath(import.meta.url)), ".."); +const referenz = JSON.parse(readFileSync(join(wurzel, "docs/carbon-referenz.json"), "utf8")); +const theme = readFileSync(join(wurzel, "features/dashboard/utils/chartTheme.ts"), "utf8"); + +const fehler = []; +const hinweise = []; + +/** Liest ein exportiertes Array von Hex-Werten aus dem Theme. */ +function hexArray(name) { + const block = theme.match(new RegExp(`export const ${name}[^=]*=\\s*\\[([\\s\\S]*?)\\]`)); + if (!block) return null; + return [...block[1].matchAll(/#[0-9a-fA-F]{6}/g)].map((m) => m[0].toLowerCase()); +} + +/** Liest einen einzelnen Hex-Wert aus einem Objektfeld im Theme. */ +function hexFeld(objekt, feld) { + const block = theme.match(new RegExp(`export const ${objekt}[^=]*=\\s*\\{([\\s\\S]*?)\\}`)); + if (!block) return null; + const treffer = block[1].match(new RegExp(`${feld}\\s*:\\s*"(#[0-9a-fA-F]{6})"`)); + return treffer ? treffer[1].toLowerCase() : null; +} + +/* -------------------------------------------------- Kategoriale Palette */ + +const palette = hexArray("KATEGORIE_FARBEN"); +const soll = referenz.kategorialePalette.werte.map((w) => w.toLowerCase()); + +if (!palette) { + fehler.push("KATEGORIE_FARBEN nicht gefunden"); +} else if (palette.length !== soll.length) { + fehler.push(`Palette hat ${palette.length} Farben, Carbon hat ${soll.length}`); +} else { + palette.forEach((farbe, i) => { + if (farbe !== soll[i]) { + fehler.push(`Palette Position ${i + 1}: ${farbe} — Carbon: ${soll[i]}`); + } + }); +} + +/* ------------------------------------------------------- Neutrale Werte */ + +const neutraleZuordnung = { + grid: "gitterlinie", + achse: "achsenlinie", + textSekundaer: "achsenbeschriftung", + text: "textPrimaer", +}; + +for (const [feld, refName] of Object.entries(neutraleZuordnung)) { + const ist = hexFeld("CHART", feld); + const sollWert = referenz.neutraleWerte[refName].wert.toLowerCase(); + if (ist === null) fehler.push(`CHART.${feld} nicht gefunden`); + else if (ist !== sollWert) { + fehler.push( + `CHART.${feld} = ${ist} — Carbon ${referenz.neutraleWerte[refName].token}: ${sollWert}` + ); + } +} + +/* ---------------------------------------------------------------- Tooltip */ + +const tooltipBg = hexFeld("CHART", "tooltipBg"); +const tooltipFg = hexFeld("CHART", "tooltipFg"); +if (tooltipBg !== referenz.tooltip.hintergrund.wert.toLowerCase()) { + fehler.push( + `CHART.tooltipBg = ${tooltipBg} — Carbon ${referenz.tooltip.hintergrund.token}: ${referenz.tooltip.hintergrund.wert}` + ); +} +if (tooltipFg !== referenz.tooltip.text.wert.toLowerCase()) { + fehler.push(`CHART.tooltipFg = ${tooltipFg} — Carbon: ${referenz.tooltip.text.wert}`); +} + +/* ------------------------------------------------- Achsen-Tickanzahl */ + +const raster = theme.match(/nettesRaster\(\s*\n?\s*max: number,\s*\n?\s*maxIntervalle = (\d+)/); +if (raster && Number(raster[1]) !== referenz.achsen.ticksYAchse) { + fehler.push( + `nettesRaster maxIntervalle = ${raster[1]} — Carbon numberOfTicks (y): ${referenz.achsen.ticksYAchse}` + ); +} + +/* -------------------------------------------- Bewusste Abweichungen melden */ + +const balken = theme.match(/maxBreite:\s*(\d+)/); +const abwBalken = referenz.bewusstAbweichend.balkenMaxBreitePx; +if (balken) { + if (Number(balken[1]) !== abwBalken.hier) { + fehler.push( + `BALKEN.maxBreite = ${balken[1]}, dokumentiert ist ${abwBalken.hier}. ` + + `Entweder den Wert zurücksetzen oder docs/carbon-referenz.json nachziehen.` + ); + } else { + hinweises(`Balkenbreite ${abwBalken.hier} px statt Carbon ${abwBalken.carbon} px`, abwBalken.grund); + } +} +function hinweises(titel, grund) { + hinweise.push({ titel, grund }); +} +hinweises( + "Feste Farbe je Kategorie statt n-Serien-Palette", + referenz.bewusstAbweichend.festeKategorieFarben.grund +); + +/* ------------------------------------------------------------- Ausgabe */ + +console.log(`Carbon-Abgleich gegen ${referenz.quelle.version} (${referenz.quelle.geprueftAm})\n`); + +if (hinweise.length) { + console.log("Bewusste Abweichungen (dokumentiert, kein Fehler):"); + for (const h of hinweise) console.log(` · ${h.titel}\n ${h.grund}\n`); +} + +if (fehler.length) { + console.error(`${fehler.length} Abweichung(en) von Carbon:`); + for (const f of fehler) console.error(` ✗ ${f}`); + console.error("\nEntweder chartTheme.ts korrigieren oder die Abweichung in"); + console.error("docs/carbon-referenz.json unter 'bewusstAbweichend' begründen."); + process.exit(1); +} + +console.log("✓ Palette, neutrale Werte, Tooltip und Achsenraster stimmen mit Carbon überein.");