--- id: WP-0017 title: whynot-design Adoption — Phase 1 (Tokens + CSS) status: finished phase: 17-of-n created: "2026-05-23" depends_on: WP-0016 --- # WP-0017 — whynot-design Adoption · Phase 1 (Tokens + CSS) Übernahme des `whynot-design`-Systems (`~/whynot-design`, gitea `whynot/whynot-design`) in vergabe-teilnahme in **Phase 1: Tokens + CSS-Variablen**. Keine Komponenten-Portierung. Bestehende Django-Partials und Tailwind-Komponentenklassen bleiben markup-seitig unverändert und werden durch den CSS-Variablen-Tausch automatisch retoniert. **Strategiebeschluss vom 2026-05-23:** zweistufige Adoption. - Phase 1 (dieser Workplan): nur Tokens + CSS. whynot-spezifische Komponenten (Lit Web Components) sind upstream in Arbeit und werden separat adoptiert. - Phase 2 (eigener Workplan, kommt später): Komponenten-Adoption sobald whynot-design die fehlenden Atome (`Card`, `Modal`, `Input`, `Table`, `Toast`) als Lit Web Components ausliefert. **Hintergrund:** ausführliche Analyse der Strategie-Optionen, Komponenten- Inventar und Lücken-Liste in `history/2026-05-23-whynot-design-cross-framework-analysis.md`. **Designentscheidungen für Phase 1:** - Distribution: **Vendoring** über Sync-Skript (kein npm/gitea SSH im Docker-Build). - Rollout: **Big-Bang** — eine PR ersetzt die komplette `brand-*`-Palette vergabe-weit. Markup-Änderungen sind nicht erforderlich. - `btn-danger`: **Off-Spec-Rot** behalten (lokale `--danger`-Variable), bis whynot-design eine kanonische Lösung definiert. **Pinned upstream:** commit `9419f166ce395858f55b10a5c72268a1fe9fc9d2` (Stand 2026-05-23; einziger Commit im whynot-design-Repo). --- ```task id: WP-0017-T01 title: Vendor-Sync-Skript + initiale Vendor-Übernahme status: done Ziel: deterministisches Pull der whynot-design CSS-/Token-Quellen aus einem gepinnten Commit nach `static/src/vendor/whynot-design/`, ohne Docker-Build SSH-Zugang zu gitea zu geben. **`scripts/sync-whynot-design.sh`** — neu anlegen: ```bash #!/usr/bin/env bash # Synchronisiert die Vendor-Kopie des whynot-Design-Systems aus einem gepinnten # Upstream-Commit. Quelle: ~/whynot-design (Worktree) oder Klone aus gitea. # # Aufruf: ./scripts/sync-whynot-design.sh [] # Default: liest .whynot-design-ref aus dem Vendor-Verzeichnis. set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" VENDOR_DIR="$ROOT/static/src/vendor/whynot-design" REF_FILE="$VENDOR_DIR/.whynot-design-ref" SRC_REPO="${WHYNOT_DESIGN_SRC:-$HOME/whynot-design}" REF="${1:-}" if [[ -z "$REF" && -f "$REF_FILE" ]]; then REF="$(cat "$REF_FILE")" fi if [[ -z "$REF" ]]; then echo "Usage: $0 (or write a ref to $REF_FILE)" >&2 exit 2 fi if [[ ! -d "$SRC_REPO/.git" ]]; then echo "Quelle nicht gefunden: $SRC_REPO" >&2 echo "Setze WHYNOT_DESIGN_SRC oder klone gitea:whynot/whynot-design dorthin." >&2 exit 1 fi mkdir -p "$VENDOR_DIR/tokens" git -C "$SRC_REPO" show "$REF:src/styles/colors_and_type.css" \ > "$VENDOR_DIR/colors_and_type.css" for f in colors.json type.json spacing.json index.json; do git -C "$SRC_REPO" show "$REF:tokens/$f" > "$VENDOR_DIR/tokens/$f" done git -C "$SRC_REPO" rev-parse "$REF" > "$REF_FILE" echo "Vendor synced → $VENDOR_DIR (ref: $(cat "$REF_FILE"))" ``` Ausführbar machen + initial syncen: ```bash chmod +x scripts/sync-whynot-design.sh ./scripts/sync-whynot-design.sh 9419f166ce395858f55b10a5c72268a1fe9fc9d2 ``` Erwartetes Resultat: `static/src/vendor/whynot-design/colors_and_type.css`, `tokens/*.json`, `.whynot-design-ref` mit dem Commit-Hash. **`Makefile`** — Target ergänzen: ```make .PHONY: sync-whynot-design sync-whynot-design: ./scripts/sync-whynot-design.sh ``` Commit-Hygiene: der Vendor-Inhalt wird **eingecheckt** (kein `.gitignore`). Diffs gegen den Vendor sind Teil des Review-Surfaces beim nächsten Bump. ``` ```task id: WP-0017-T02 title: CSS-Integration in static/src/main.css status: done Ziel: whynot-Tokens werden global verfügbar, Tailwind-`@theme`-Mapping exponiert sie als Utility-Klassen, vergabe-spezifisches Brand-Blau entfällt. **`static/src/main.css`** — vollständig ersetzen (Reihenfolge ist wichtig: whynot-CSS vor Tailwind, damit `@import url(...)` für IBM Plex am Anfang der generierten Datei landet): ```css /* whynot-design Tokens & semantische Element-Styles (gepinnt via scripts/sync-whynot-design.sh; siehe .whynot-design-ref). */ @import "./vendor/whynot-design/colors_and_type.css"; @import "tailwindcss"; /* Tailwind v4 scannt diese Pfade für Utility-Klassen. Muss synchron mit der Dockerfile `assets`-Stage bleiben. */ @source "../../vergabe_teilnahme/templates"; /* whynot-Tokens → Tailwind-Theme. Erlaubt: bg-paper, text-ink, border-line, bg-paper-2, text-ink-3, … */ @theme { --color-ink: var(--ink); --color-ink-2: var(--ink-2); --color-ink-3: var(--ink-3); --color-ink-4: var(--ink-4); --color-ink-5: var(--ink-5); --color-line: var(--line); --color-line-strong: var(--line-strong); --color-line-soft: var(--line-soft); --color-paper: var(--paper); --color-paper-2: var(--paper-2); --color-paper-3: var(--paper-3); --color-hi: var(--hi); --color-hi-2: var(--hi-2); --color-hi-ink: var(--hi-ink); } /* Off-Spec — vergabe-lokal, bis whynot-design eine kanonische destruktive Farbe definiert. Siehe history/2026-05-23-whynot-design-cross-framework-analysis.md §4 "btn-danger". */ :root { --danger: #B22222; --danger-fg: #FFFFFF; } @layer base { html { font-family: var(--ff-sans, ui-sans-serif), system-ui, sans-serif; } } @layer components { /* Karten / Sheets — whynot: ohne Shadow, hairline border */ .card { @apply bg-paper rounded border border-line p-6; } /* Buttons — whynot: 3 Varianten + off-spec danger */ .btn-primary { @apply bg-ink text-paper px-4 py-2 rounded hover:bg-ink-2 transition-colors; } .btn-secondary { @apply bg-paper text-ink border border-line px-4 py-2 rounded hover:bg-paper-2 transition-colors; } .btn-ghost { @apply text-ink-3 px-3 py-2 rounded hover:bg-paper-2; } .btn-danger { background: var(--danger); color: var(--danger-fg); @apply px-4 py-2 rounded transition-colors; } .btn-danger:hover { filter: brightness(0.92); } /* Field-Row — Label/Value Grid */ .field-row { @apply grid grid-cols-3 gap-4 py-3 border-b border-line-soft last:border-0; } .field-label { @apply text-sm font-medium text-ink-3 col-span-1; } .field-value { @apply text-sm text-ink col-span-2; } /* Phasen-Indikatoren — vergabe-Semantik (todo/active/done/warn), in whynot-Palette übersetzt. `phase-warn` nutzt `--hi` (annotation yellow). */ .phase-badge { @apply inline-flex items-center justify-center w-7 h-7 rounded-full text-sm font-bold; } .phase-todo { @apply inline-flex items-center justify-center w-7 h-7 rounded-full text-sm font-bold bg-paper-3 text-ink-4; } .phase-active { @apply inline-flex items-center justify-center w-7 h-7 rounded-full text-sm font-bold bg-ink text-paper; } .phase-done { @apply inline-flex items-center justify-center w-7 h-7 rounded-full text-sm font-bold bg-ink-3 text-paper; } .phase-warn { background: var(--hi); color: var(--hi-ink); @apply inline-flex items-center justify-center w-7 h-7 rounded-full text-sm font-bold; } /* Titel / Sektionen */ .section-title { @apply text-base font-semibold text-ink mb-4; } .page-title { @apply text-2xl font-medium text-ink tracking-tight; } /* Formulare */ .form-input { @apply w-full rounded border border-line px-3 py-2 text-sm bg-paper focus:outline-none focus:border-ink transition-colors; } .form-label { @apply block text-sm font-medium text-ink-2 mb-1; } /* Tabellen */ .table-base { @apply w-full text-sm text-left; } .table-header { @apply bg-paper-2 text-ink-3 font-medium text-xs uppercase tracking-wide; } .table-row { @apply border-t border-line-soft hover:bg-paper-2 transition-colors; } /* Sidebar */ .sidebar-link { @apply flex items-center px-3 py-2 rounded text-sm text-ink-2 hover:bg-paper-2 transition-colors; } .sidebar-link-active { @apply bg-paper text-ink font-medium; box-shadow: inset 0 0 0 1px var(--line); } .sidebar-section-btn { @apply w-full flex items-center justify-between px-3 py-2 text-xs font-semibold text-ink-4 uppercase tracking-wide hover:text-ink-2; } } ``` **Kommentar zu Border-Radii:** whynot-Hausregel verlangt 0–4px für Cards/Sheets, 8px nur für große Modale. Tailwind `rounded` (= 4px) erfüllt das. Keine `rounded-xl`/`rounded-lg` mehr in den Komponenten-Klassen. **Kommentar zu Shadows:** whynot-Regel "no shadows on cards". `shadow-sm` wurde aus `.card` entfernt; Sidebar-Active nutzt `inset` border statt Schatten. **Kommentar zu Fonts:** `colors_and_type.css` zieht IBM Plex via `@import url("https://fonts.googleapis.com/...")`. Build-Container und Browser brauchen Internet-Zugriff zu Google Fonts. Für air-gapped Deployment muss das später durch self-hosting ersetzt werden — ist heute nicht relevant (coulombcore k3s hat Internet-Egress). ``` ```task id: WP-0017-T03 title: Base-Template — Body-Hintergrund auf whynot-Palette status: done **`vergabe_teilnahme/templates/base.html`** — Body-Klasse anpassen: ```diff - + ``` Damit der Default-Text vom whynot-Ink-Ton kommt und der Canvas auf Sheet-Beige (`#FAFAF7`) liegt, nicht auf Slate-Blau-50. Keine weiteren Markup-Änderungen in Phase 1. Alle `bg-slate-*`/`text-slate-*` Utilities in den Page-Templates werden in T05 (Smoke-Test) gesichtet und nur dann gepatcht, wenn sie visuell brechen — Tailwinds Slate-Skala existiert weiterhin parallel zur whynot-Palette. ``` ```task id: WP-0017-T04 title: Build + Static-Asset-Prüfung status: done Lokaler Build: ```bash npm ci npm run build ls -lh static/dist/main.css ``` Erwartet: `main.css` enthält die whynot-CSS-Variablen am Anfang, gefolgt von generierten Tailwind-Utilities. Größe steigt moderat (whynot-CSS ≈ 8 KB unkompressed). **Sanity-Check** der generierten Utilities: ```bash grep -c "bg-ink\|bg-paper\|text-ink\|border-line" static/dist/main.css # Erwartet: > 0 (Beweis, dass @theme-Mapping aktiv ist) grep -c "color-brand-500\|3b5bdb" static/dist/main.css # Erwartet: 0 (Alt-Palette komplett raus) ``` Falls eine vergabe-Template-Datei noch `bg-brand-*` o.ä. nutzt: in T05 adressieren (visueller Bruch wird dort sichtbar). ``` ```task id: WP-0017-T05 title: Big-Bang Smoke-Test — visueller Durchlauf aller Hauptseiten status: done Dev-Server starten und durch die wichtigsten Views klicken. Bei jedem visuellen Bruch (Kontrast, weiße Schrift auf weißem Grund, harte Farb-Fremdkörper) eine kurze Notiz machen und im selben Task patchen. ```bash docker-compose -f docker-compose.dev.yml up -d # oder: uv run python manage.py runserver ``` Zu prüfende Views (Mindestliste — Anordnung folgt der Sidebar): - [ ] Dashboard / Ausschreibungs-Übersicht - [ ] Ausschreibung Detail (Phasen-Nav sichtbar) - [ ] Lose-Liste + Detail - [ ] Aufgaben-Liste - [ ] Dokumente-Übersicht - [ ] Preise-Auswertung - [ ] Partner-Bibliothek - [ ] Marktbegleiter - [ ] Nachbetrachtung - [ ] Feedback-Modal (Trigger via Bug-Button) - [ ] Freigabe-Modal (auf einem freigabefähigen Objekt) - [ ] Search-Results (HTMX-Suche in der Top-Bar) **Typische Brüche, die zu erwarten sind:** 1. Direkt im Template inline gesetzte `bg-blue-*`, `text-blue-*`, `bg-brand-*` — in den whynot-Äquivalenten ersetzen (`bg-ink`, `text-ink`, `bg-hi`). 2. `shadow-sm`/`shadow-md` auf Cards — entfernen (whynot-Regel: keine Shadows auf Cards, nur auf Popovers). 3. `rounded-lg`/`rounded-xl` auf nicht-Modal-Elementen — auf `rounded` (=4px) reduzieren. 4. Fokus-Ringe in Brand-Blau — der `.form-input`-Style nutzt jetzt Border-Ink; Tailwind-Default-Ring kann noch blau sein, ggf. globalen Ring-Reset ergänzen. Screenshots der Hauptseiten **vor und nach** dem Swap in `history/2026-05-23-whynot-design-phase1-screenshots/` ablegen, als Sichtprüfungs-Beleg. ``` ```task id: WP-0017-T06 title: Doku-Update und Phase-2-Pflock status: done **`wiki/`** — neue Datei `wiki/DesignSystem.md` mit knappem Inhalt: ```markdown # Design System vergabe-teilnahme nutzt das `whynot-design`-System (gitea `whynot/whynot-design`) als visuelle Basis. **Phase 1 (aktuell, ab WP-0017):** nur Tokens + CSS-Variablen, vendoring nach `static/src/vendor/whynot-design/`. Sync via `make sync-whynot-design`. Aktuell gepinnter Commit: siehe `static/src/vendor/whynot-design/.whynot-design-ref`. **Phase 2 (offen):** Komponenten-Adoption sobald upstream Lit Web Components und die fehlenden Atome (Card, Modal, Input, Table, Toast) ausliefert. Eigener Workplan wird zu diesem Zeitpunkt angelegt. **Lokale Abweichungen** vom whynot-System (dokumentiert in `main.css`): - `btn-danger` mit Off-Spec-Rot (`#B22222`) — whynot definiert keine destruktive Farbe; vergabe-Nutzung erfordert sie für Löschen-Aktionen. Wird zurückgebaut, sobald upstream eine kanonische Lösung definiert. **Hintergrund:** Strategie-Analyse + Komponenten-Lücken in `history/2026-05-23-whynot-design-cross-framework-analysis.md`. ``` **`CLAUDE.md` / `.claude/rules/stack-and-commands.md`** — Stack-Eintrag ergänzen um `whynot-design` und den Sync-Befehl: ```markdown ## Stack - **Language:** Python 3.12 (Django 6), Node 22 (Vite/Tailwind v4) - **Key deps:** Django, htmx, Alpine.js, Tailwind v4, whynot-design (vendored) ## Dev Commands … # Design-System-Vendor aktualisieren (whynot-design Pin bumpen) make sync-whynot-design ``` **Workplan-Index** in `workplans/README.md` ist bereits 12-Workplan-zentriert und veraltet (es gibt jetzt 17). Nicht in diesem WP anpassen — eigener Aufräum-WP, falls der Index wieder verbindlich werden soll. ``` --- ## Nicht in diesem Workplan - Komponenten-Portierung (React-JSX → Django-Partials) — entfällt; wird durch upstream Lit Web Components in Phase 2 obsolet. - `whynot-design-django`-Repo — entfällt aus dem gleichen Grund. - Neue Atome (Card, Modal, Input, Table, Toast als DS-Komponenten) — kommen upstream; bis dahin vergabe-lokal über Tailwind-Klassen. - Visuelle Regression-Tests (Playwright) — wäre sinnvoll, aber nicht Voraussetzung für Phase 1. - Markup-Änderungen in Page-Templates über das in T05 Notwendige hinaus. ## Definition of Done - `static/src/vendor/whynot-design/colors_and_type.css` vorhanden, `.whynot-design-ref` enthält gepinnten Commit-Hash. - `npm run build` erfolgreich, `static/dist/main.css` ohne `#3b5bdb` (Alt-Brand-Blau) und mit aktiven `bg-ink`/`bg-paper`-Utilities. - Alle 12 Smoke-Test-Views in T05 visuell durchgegangen, Brüche gepatcht, Screenshots in `history/` abgelegt. - `wiki/DesignSystem.md` existiert und referenziert History-Artefakt + Phase-2-Bedingung.