diff --git a/CLAUDE.md b/CLAUDE.md index b400b67..17c0ee9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -11,7 +11,7 @@ Dieses Dokument beschreibt Architektur, Konventionen und wichtige Implementierun - **Einstiegspunkt:** `index.html` - **Styles:** `css/styles.css` (Layout & Komponenten) + `css/tokens.css` (Design-Tokens) - **Logik:** `js/app.js` (eine einzige Datei) -- **Aktuelle Version:** `v65` (Script-Tag: ` + ``` ### Externe Aufrufe @@ -66,7 +66,7 @@ Die App hat keinen Build-Schritt – getestet wird **die ausgelieferte App**, al ```bash npm install # nur Dev: @playwright/test (exakt gepinnt) npx playwright install chromium # einmalig -npm test # 371 Tests, ~145 s +npm test # 376 Tests, ~150 s npm run test:ui # interaktiver Modus ``` @@ -116,7 +116,9 @@ Spätere Module hängen sich an `inventory` an: Modul 3a (Clearing) bewertet die ### View-Umschaltung -Sieben Views: `home` (Hero + Akkordeon + Modul-Grid), `kompass` (Daten-Kompass), `inventory` (Inventar/Clearing/Qualität-Tabs), `governance` (RACI + Reifegrad), `pseudo` (Textbereinigung, drei Tabs), `wissen` (Wissens- & Methodik-Center) und `vorlagen` (Vorlagen & Musterdokumente). Zentral über `showView(name)` umgeschaltet (blendet die Home-Elemente per `style.display` aus, toggelt `.hidden` an `#kompass-view`/`#inventory-view`/`#governance-view`/`#pseudo-view`/`#wissen-view`/`#vorlagen-view`, blendet den Phase-4&5-Beratungsblock `.consult-cta` außerhalb von `home` aus, scrollt nach oben). `navTo(target)` kapselt die Navigations-Einstiege (Topbar-Brand → home, Hero-CTA + Topbar-„Loslegen" → kompass, Sidebar-Links `data-view`, Modul-Buttons; „Dateninventar" öffnet ohne Daten das Erklär-Modal, `kompass`/`governance`/`pseudo`/`wissen`/`vorlagen` jederzeit). +**Startseiten-Abschnitte tragen `.home-only`.** `showView()` blendet sie über genau eine Abfrage aus (`document.querySelectorAll('.home-only')`) statt über eine Liste von IDs plus einen Sonderfall für `.consult-cta` – bis v65 wäre ein neuer Startseiten-Abschnitt auf allen Unterseiten stehen geblieben, ohne dass etwas rot geworden wäre. Aktuell: `#hero`, `#hero-steps`, `#about-accordion`, `#module-grid`, `.consult-cta`. Ein Test geht alle sechs Unterseiten durch und verlangt, dass keines davon sichtbar bleibt. + +Sieben Views: `home` (Hero + Schritte + Akkordeon + Modul-Grid), `kompass` (Daten-Kompass), `inventory` (Inventar/Clearing/Qualität-Tabs), `governance` (RACI + Reifegrad), `pseudo` (Textbereinigung, drei Tabs), `wissen` (Wissens- & Methodik-Center) und `vorlagen` (Vorlagen & Musterdokumente). Zentral über `showView(name)` umgeschaltet (blendet die Home-Elemente per `style.display` aus, toggelt `.hidden` an `#kompass-view`/`#inventory-view`/`#governance-view`/`#pseudo-view`/`#wissen-view`/`#vorlagen-view`, blendet den Phase-4&5-Beratungsblock `.consult-cta` außerhalb von `home` aus, scrollt nach oben). `navTo(target)` kapselt die Navigations-Einstiege (Topbar-Brand → home, Hero-CTA + Topbar-„Loslegen" → kompass, Sidebar-Links `data-view`, Modul-Buttons; „Dateninventar" öffnet ohne Daten das Erklär-Modal, `kompass`/`governance`/`pseudo`/`wissen`/`vorlagen` jederzeit). **Status-Dashboard (Startseite):** `#dashboard` erscheint auf `home`, sobald Daten vorliegen (`hasAnyData()`), sonst bleibt es per `.hidden` ausgeblendet (Erstnutzer sehen Hero + Module). `refreshDashboard()` (in `showView('home')` und beim Laden nach `loadState()`) toggelt Sichtbarkeit und ruft `renderDashboard()`, das vier Live-Kennzahl-Karten füllt: Daten-Kompass (`kompassOverall()` % + `kompassAmpel`), Governance (`reifegrad().score` % + `reifeAmpel`, „offen" ohne Antworten), Inventar (Anzahl + Ø `completeness`), Risiko-Clearing (Ampelverteilung via `ensureAllClearing()`). Jede Karte ist ein `[data-go]`-Schnellsprung. **Wichtig:** `[data-go]` ist **delegiert** an `document` gebunden (nicht pro Element), damit dynamisch gerenderte Dashboard-Karten ebenfalls greifen. @@ -446,6 +448,8 @@ Der Verlauf steckt **additiv** in `buildProjectJSON()` (kein Schema-Bump – äl Geführter Durchlauf durch die Bausteine (`TOUR_STEPS`, `tour = { i, active }`). `startTour()` / `tourGo(±1)` / `endTour()`; `renderTour()` navigiert je Schritt via `navTo()`, hebt ein Ziel per `.tour-highlight` hervor (z-index über dem abdunkelnden `#tour-layer`) und baut die Karte `#tour-card` (`role="dialog"`, `aria-modal`). Escape und Pfeiltasten sind gebunden. **`trapFocus()` greift auch hier** – die Funktion sucht in `.modal-backdrop` *und* `.tour-layer`: `aria-modal` blendet den Rest für Screenreader aus, per Tab konnte man ihn vorher trotzdem verlassen und landete in einer Seite, die für sie gar nicht existiert. +> ⚠️ **Die Stapelreihenfolge ist dreiteilig und hängt an einer Eigenheit von `position: fixed`.** Ein fixiertes Element bildet einen **eigenen Stapelkontext** – die Karte in `#tour-layer` kann dessen `z-index` also nie überbieten, egal welchen Wert sie selbst trägt. Bis v65 stand das Abdunkeln am Layer (1200) und `.tour-highlight` darüber (1201); das hervorgehobene Zielelement zeichnete damit **in die Karte hinein** (bei Schritt 3 stand der Fragebogentext über Titel und Knöpfen) und fing dort auch die Klicks ab. Seit v66 gilt: **Abdunkeln `body.tour-on::before` (1200) < Hervorhebung (1201) < Layer mit Karte (1202)**. Das Abdunkeln muss dafür *außerhalb* des Layers liegen – daher die Body-Klasse, die `startTour()` setzt und `endTour()` entfernt (sonst bliebe die Seite dauerhaft dunkel). + **Bewusst kein Auto-Start als Modal beim ersten Laden** – das nimmt Erstnutzern die Kontrolle. Stattdessen ein wegklickbarer Hinweis `#tour-hint` auf der Startseite (`refreshTourHint()` in `showView('home')` + beim Laden) und ein Dauer-Einstieg `#sidebar-tour`. Gesehen-Status unter `datenlotse_tour` (`'done'`). Schritte mit `needsData` brauchen ein Inventar. Statt still Beispieldaten zu laden – eine Nebenwirkung, die niemand bestellt hat – bietet der Schritt den Import an (`#tour-sample`) und lässt die Entscheidung beim Menschen. Ein Test prüft, dass jedes `target` in der jeweiligen View wirklich existiert; sonst zeigt der Rundgang ins Leere, sobald sich Markup ändert. @@ -511,7 +515,9 @@ Jede Unterseite trägt ihren Kopf in einem eigenen weißen Container; die Starts - **`.view-hero`** umschließt `.inventory-header` **und** den direkt folgenden `.inventory-hint`. Der Hinweis verliert darin seinen eigenen Kasten (sonst stünde Weiß in Weiß) und wird nur durch eine Linie abgesetzt. - **Die Aktionsbuttons bekommen im Hero eine eigene Zeile** (`flex-direction: column`). Nebeneinander hingen sie je nach Länge des Untertitels auf halber Höhe – jede Unterseite sah anders aus. Der Rhythmus ist jetzt überall: Badge → Titel → Untertitel → Aktionen → Einleitung. -- **Die statischen Seiten teilen den Kopf** (`.static-hero`, erzeugt in `tools/generate-wissen.js`): Brotkrumen, Titel und Lead stehen im selben weißen Container. Sie sind aus dem Fuß und aus dem Wissens-Center verlinkt und sollen wie dasselbe Produkt aussehen, nicht wie ein angehängtes Dokument. Ein Test prüft den Container je Seite. +- **Der Hero der Startseite trägt seit v66 nur noch den Einstieg**: Logo, Titel, Einleitung, Aktion und die Import-Zeile. Die vier Schritt-Chips und die Local-First-Zusage stehen darunter in einem **eigenen Abschnitt** (`#hero-steps`, `.steps-section`/`.steps-panel`, Überschrift als `

` – je Ansicht bleibt genau eine `

`). Vorher waren es sieben Bausteine in einem Container, die einander die Aufmerksamkeit nahmen. +- **Titel und Einleitung benennen den Zweck.** Bis v65 stand dort „Datenmanagement verstehen, aufbauen, vertiefen" – wofür das Werkzeug gebaut ist, nämlich Daten zu **öffnen**, kam weder in der Überschrift noch im Einleitungssatz vor. Beides sagt es jetzt ausdrücklich (und die Meta-Beschreibungen ebenso); ein Test hält Titel *und* Einleitungssatz fest, weil eine Überschrift ohne den Satz darunter nur die halbe Aussage wäre. +- **Die statischen Seiten teilen den Kopf** (`.static-hero`, erzeugt in `tools/generate-wissen.js`) – und seit v66 auch den **Fuß**: der Generator schrieb `class="footer"`, wofür es gar keine Regel gibt, die Linkzeile stand also ungestylt am unteren Rand. Es ist dieselbe Leiste wie in der App, also dieselbe Klasse `.site-footer` samt Marke, Trennern und GitHub-Zeichen. Ein Test prüft je Seite Grundfarbe, obere Linie und die Marke.: Brotkrumen, Titel und Lead stehen im selben weißen Container. Sie sind aus dem Fuß und aus dem Wissens-Center verlinkt und sollen wie dasselbe Produkt aussehen, nicht wie ein angehängtes Dokument. Ein Test prüft den Container je Seite. - Der Seitentitel bleibt das **einzige `

`** der Ansicht (ein Test prüft genau eine sichtbare `h1` je View) – der Hero ist ein Container, keine zusätzliche Überschrift. --- @@ -635,6 +641,7 @@ Nach Änderungen an `app.js` `?v=N` im Script-Tag **und** die `v{N}` im Footer e | Kompass-Verlauf | `kompassSnapshot()`, `kompassTrend()`, `renderKompassHistory()`, `loadKompassHistory()`, `clearKompassHistory()`, `kompassHistory` (State) | `#kompass-history`, `#kompass-snap`, `#kompass-hist-clear`, `.khist-bar`, `datenlotse_kompass_verlauf` | | Prüfwerkzeuge & Normtexte | `PRUEF_WERKZEUGE`, `renderWissen()` | `#wissen-sec-tools`, `#wissen-tools` | | Hero je Ansicht | `.view-hero` (App), `.hero-panel` (Start), `.static-hero` (erzeugte Seiten) | `.inventory-header`, `.inventory-hint`, `.static-crumbs` | +| Startseite: Hero + Schritte getrennt | `showView()` über `.home-only` | `#hero`, `#hero-steps`, `.steps-panel`, `.steps-title`, `.hero-steps`, `.hero-trust` | | Statische Seiten & Datenschutz | `tools/generate-wissen.js`: `seite()`, `seiten()`, `eigenstaendigeSeiten()` | `/wissen/…`, `/datenschutz/`, `sitemap.xml` | | Anonyme Seitenzählung | GoatCounter – einziger externer Aufruf, im Test abgeklemmt | `gc.zgo.at`, `datenlotse.goatcounter.com` | | Persistenz | `saveState()`, `loadState()`, `clearState()` | `datenlotse_*`, `#reset-data-btn` | @@ -679,6 +686,7 @@ Nach Änderungen an `app.js` `?v=N` im Script-Tag **und** die `v{N}` im Footer e | v16 | Daten-Kompass (Herzstück) – eigene View + Hero-Haupt-CTA (Topbar-„Loslegen" zeigt ebenfalls darauf): ausführliche Open-Data-Reifegrad-Checkliste nach ODRA / EU Open Data Maturity / 5-Sterne / DCAT-AP.de / DSGVO·FAIR (7 Dimensionen, Quellenangaben), Status je Item mit Score & Ampel, Vorbelegung aus dem App-Stand, adaptive Sprünge in die passenden Bausteine, Persistenz (`datenlotse_kompass`) und PDF-Export; leeres „Loslegen"-Platzhalter-Modal entfernt | | v17 | Weiterer Ausbau (1/4) – Inventar Suche, Filter & Sortierung: `renderInventory()` in `renderInventory()` + `renderInventoryBody()` aufgeteilt; `.inv-controls` (Volltextsuche + Schutzbedarf-/Clearing-Ampel-Filter + Sortierung Titel/Vollständigkeit) über `invFilter`-State und `filteredInventory()`; der echte `idx` wird durch den Filter mitgeführt, sodass Editieren über gefilterten Teilmengen weiterhin den richtigen Datensatz trifft; Live-Meta „X von Y" + Empty-State | | v18 | Weiterer Ausbau (2/4) – Pseudonymisierung erweitert: drei neue Muster (Sozialversicherungsnummer, Steuer-ID *kontextgetriggert*, Kfz-Kennzeichen), Aktenzeichen um Geschäftszeichen/„Gz." und buchstabenhaltige Kerne erweitert, zusätzliche Geburtsdatum-Trigger („Geburtsdatum"/„Geburtstag"); Mapping-Export als CSV (`buildPseudoMappingCSV` + Button im Mapping-Kopf); Demo-Text und Grenzen-Liste aktualisiert; verifiziert auf Determinismus, Platzhalter-Konsistenz und Null-Falschtreffer auf neutralem Verwaltungstext | +| v66 | **Ein Darstellungs-Durchgang – und dreimal dieselbe Lehre: eine Regel, die nicht greift, fällt niemandem auf.** Der auffälligste Fund kam aus der Benutzung: im Rundgang zeichnete das hervorgehobene Zielelement **in die Karte hinein** – bei Schritt 3 stand der Fragebogentext über Titel und Knöpfen, und Klicks dort trafen den Fragebogen statt „Weiter". Ursache ist eine Eigenheit von `position: fixed`: der Layer bildet einen **eigenen Stapelkontext**, die Karte darin kann dessen `z-index` also nie überbieten, egal welchen Wert man ihr gibt. Ein höherer Wert an der Karte wäre wirkungslos geblieben – das Abdunkeln musste **aus dem Layer heraus** (jetzt `body.tour-on::before`), damit die Reihenfolge Abdunkeln < Hervorhebung < Karte überhaupt darstellbar ist. Zweiter Fund derselben Art: der Wissens-Generator schrieb `