# @ocelot-social/ui - Projektdokumentation | Ocelot.Social Documentation
Ocelot.Social Documentation

Skip to main content

- Introduction
- Neo4J

- Backend

  - GraphQL
  - neo4j-graphql-js

- Webapp (Frontend)

  - Components
  - HTML
  - SCSS
  - Vue

- Testing Guide

  - End-to-end Tests
  - Webapp (Frontend) Tests
  - Backend Tests
- Deployment
- Contributing
- Feature Specification
- Code of Conduct
- Documentation
- License

# @ocelot-social/ui - Projektdokumentation

April 5, 2026
About 56 min

On This Page

- Inhaltsverzeichnis
- - Schnellzugriff (Status)
  - Nach Thema
  - Wie dieses Dokument verwendet wird
- Fortschritt
- - Gesamtprojekt
  - Katalogisierung (Details in KATALOG.md)
  - OsButton Migration (Phase 3) ✅
  - OsIcon (Phase 4)
- Aktueller Stand
- Meilensteine
- - Phase 0: Analyse & Katalogisierung ✅
  - Phase 1: Vue 2.7 Upgrade ✅
  - Phase 2: Projekt-Setup ✅
  - Phase 3: Webapp-Integration (Validierung)
  - Phase 4: Komponenten-Migration
  - Phase 5: Finalisierung & Entkopplung
- 1. Projektziel & Vision
- 2. Tech-Stack
- - Konventionen
- 3. Build & Distribution
- - Dual-Build Strategie
  - Nutzung MIT Tailwind
  - Nutzung OHNE Tailwind
  - Branding (funktioniert für beide)
  - Webapp-Integration (Entwicklung)
  - Webapp-Aufgaben (TODO für Ocelot-Webapp)
- 4. Icon-Architektur
- - Entscheidung: Hybrid-Ansatz
  - Begründung
  - System-Icons (in Library enthalten) ✅
  - API-Design (implementiert)
  - SVG-Loading (vite-svg-icon Plugin)
  - Ocelot-Icons (separates Entry-Point)
  - Aktuelle Icon-Statistik
- 5. Design-Token & Branding
- - 3-Stufen Token-System
  - Branding-Flexibilität
  - Kompatibilität mit bestehendem System
  - CVA + Tailwind + CSS-Variablen
- 6. CI/CD & Release
- - Release-Workflow (release-please)
  - GitHub Workflows
  - Qualitätssicherung bei PRs
  - Automatische Deployments
  - GitHub Workflows (vollständige Liste)
  - Erweiterte Qualitätssicherung
  - Migrations-Absicherung
- 7. Dokumentation & DX
- - Storybook als Komponenten-Dokumentation
- 8. Migrationsstrategie
- - Grundprinzipien
  - Ablauf pro Komponente
  - Komponenten-Protokoll
  - Qualitätsanforderungen pro Komponente
  - Konsolidierungsziele
- 9. Dokumentationsstrategie
- 10. Kompatibilitätstests
- 11. Entscheidungen
- - Vision & Ziele
  - Tech-Stack
  - Build & Distribution
  - CI/CD & Release
  - Testing & Qualität
  - Design-System & Branding
  - Komponenten-API & Konventionen
  - Dokumentation
  - Migration & Prozess
- 12. Arbeitsprotokoll
- 13. Komponenten-Katalog
- - Zusammenfassung (aus KATALOG.md)
- 14. Ressourcen & Links
- 15. Dokumentationsstrategie (Details)
- - Übersicht
  - README.md Struktur
  - Generierung mit vue-component-meta
  - CI-Workflow: docs-check
  - ESLint-Regeln für Dokumentation
  - Dokumentations-Phasen
  - Nach der Migration: Datei-Transformation
- 16. Library vs. Webapp
- - Grundprinzip
  - Entscheidungs-Checkliste
  - Entscheidungsbaum
  - Quantitative Regel
  - Konkrete Beispiele
  - Composition Pattern für Grenzfälle
  - Checkliste bei neuer Komponente
- 16a. Webapp ↔ Maintenance Code-Sharing
- - Problemstellung (historisch)
  - Umgesetzte Lösung: Vollständige Entkopplung ✅ (Session 35, 2026-03-28)
  - Kriterien für Entwickler
  - Vorteile der Entkopplung
  - Spätere Evolution (optional)
  - Entscheidung
- 16b. Daten-Entkopplung (ViewModel/Mapper Pattern)
- - Problemstellung
  - Lösung: ViewModel + Mapper Pattern
  - Implementierung
  - Ordnerstruktur
  - Vorteile
  - Regeln
  - Entscheidung
- 17. Externe Abhängigkeiten
- - Übersicht
  - eslint-config-it4c
- 18. Kompatibilitätstests (Details)
- - Testmatrix
  - Werkzeuge & Strategien
  - CI Workflow: Compatibility Matrix
  - Werkzeug-Übersicht
  - Checkliste für neue Komponenten
- 19. Komplexitätsanalyse
- - Umfang nach Phasen
  - Bekannte Risikofaktoren
  - Parallelisierbarkeit
  - Aufwandstreiber pro Komponente (Phase 4)

# @ocelot-social/ui - Projektdokumentation

> Dieses Dokument dient als zentrale Planungs- und Statusübersicht für das UI-Library Subprojekt. Es ermöglicht das Pausieren und Wiederaufnehmen der Arbeit zu jedem Zeitpunkt.

## Inhaltsverzeichnis

### Schnellzugriff (Status)

Abschnitt | Beschreibung
--- | ---
Fortschritt | Visuelle Fortschrittsanzeige
Aktueller Stand | Was zuletzt erledigt wurde
Meilensteine | Phasen 0-5 mit Checklisten

### Nach Thema

VISION

# | Abschnitt
--- | ---
1 | Projektziel & Vision

TECHNISCHE GRUNDLAGEN

# | Abschnitt
--- | ---
2 | Tech-Stack
3 | Build & Distribution
4 | Icon-Architektur
5 | Design-Token & Branding

PROZESSE & QUALITÄT

# | Abschnitt
--- | ---
6 | CI/CD & Release
7 | Dokumentation & DX
8 | Migrationsstrategie
9 | Dokumentationsstrategie
10 | Kompatibilitätstests

REFERENZ & HISTORIE

# | Abschnitt
--- | ---
11 | Entscheidungen
12 | Arbeitsprotokoll
13 | Komponenten-Katalog
14 | Ressourcen & Links
15 | Dokumentationsstrategie (Details)

ABGRENZUNGEN

# | Abschnitt
--- | ---
16 | Library vs. Webapp
16a | Webapp ↔ Maintenance Code-Sharing
16b | Daten-Entkopplung (ViewModel/Mapper)
17 | Externe Abhängigkeiten
18 | Kompatibilitätstests (Details)
19 | Komplexitätsanalyse

### Wie dieses Dokument verwendet wird

Zum Fortsetzen der Arbeit:

> "Lass uns am @ocelot-social/ui Projekt weiterarbeiten" (packages/ui)

Nach jeder Session aktualisieren:

- "Fortschritt" – Balkendiagramme aktualisieren
- "Aktueller Stand" – Zuletzt erledigte Aufgaben
- "Meilensteine" – Checklisten abhaken
- §12 "Arbeitsprotokoll" – Neue Einträge hinzufügen
- `KATALOG.md` – Komponenten-Status pflegen

## Fortschritt

### Gesamtprojekt

    Phase 0: ██████████ 100% (6/6 Aufgaben) ✅
    Phase 1: ██████████ 100% (6/6 Aufgaben) ✅
    Phase 2: ██████████ 100% (26/26 Aufgaben) ✅
    Phase 3: ██████████ 100% (24/24 Aufgaben) ✅ - Webapp-Integration komplett
    Phase 4: █████████░  85% (23/27 Aufgaben) - Tier 1 ✅, Tier A ✅, Infra ✅, OsBadge ✅, ds-grid ✅, ds-table→HTML ✅, OsNumber ✅, OsModal ✅, ds-radio→HTML ✅ | Tier B ✅, OcelotInput ✅, OcelotSelect ✅, OsMenu ✅ | 0 ds-* Tags verbleibend
    Phase 5: ███░░░░░░░  29% (2/7 Aufgaben) - Maintenance-App entkoppelt ✅
    ───────────────────────────────────────
    Gesamt:  █████████░  91% (87/96 Aufgaben)

### Katalogisierung (Details in KATALOG.md)

    Webapp:     ██████████ 100% (139 Komponenten erfasst)
    Styleguide: ██████████ 100% (38 Komponenten erfasst)
    Analyse:    ██████████ 100% (Button, Modal, Menu detailiert)

### OsButton Migration (Phase 3) ✅

    Scope gesamt:     133 <os-button> Tags in 79 Webapp-Dateien
    ├─ Migriert:       133 Buttons (100%) ✅
    ├─ <base-button>:    0 verbleibend in Templates
    ├─ <ds-button>:      0 verbleibend in Templates
    └─ Cleanup:        Snapshots/Tests müssen aktualisiert werden

    OsButton Features:
    ├─ variant:     ✅ primary, secondary, danger, warning, success, info, default
    ├─ appearance:  ✅ filled, outline, ghost
    ├─ size:        ✅ sm, md, lg, xl
    ├─ disabled:    ✅ mit hover/active-Override (nur as="button")
    ├─ icon:        ✅ slot-basiert (icon-system-agnostisch)
    ├─ circle:      ✅ rounded-full, größenabhängig (p-1.5 bis p-3)
    ├─ loading:     ✅ animated SVG spinner, aria-busy (Milestone 4b)
    └─ as:          ✅ polymorphes Rendering (button/a/NuxtLink/RouterLink)

    as-Prop Migration:  15 <nuxt-link>/<a>-Wrapper in 15 Webapp-Dateien → as="nuxt-link"/as="a"

### OsIcon (Phase 4)

    OsIcon Features:
    ├─ name:       ✅ System-Icon per Name (check, close, plus)
    ├─ icon:       ✅ Custom Vue-Komponente (hat Vorrang vor name)
    ├─ size:       ✅ xs, sm, md, lg, xl, 2xl (em-basiert)
    ├─ a11y:       ✅ decorative (default) / semantic (mit aria-label)
    ├─ color:      ✅ fill-current (erbt von Parent)
    └─ svg-plugin: ✅ vite-svg-icon (SVG → Vue Component via ?icon)

    System-Icons:
    ├─ check.svg   (Checkmark)
    ├─ close.svg   (Close/X)
    └─ plus.svg    (Plus/Add)

    Ocelot-Icons (separates Entry-Point):
    └─ 82 Icons (Feature-Icons + Kategorie-Icons aus Webapp migriert)

    OsSpinner:
    ├─ size:       ✅ xs, sm, md, lg, xl, 2xl (em-basiert)
    ├─ color:      ✅ currentColor (erbt von Parent)
    ├─ a11y:       ✅ role="status", aria-label="Loading" (customizable)
    ├─ decorative: ✅ aria-hidden="true" suppresses role/aria-label
    ├─ os-button:  ✅ OsButton nutzt OsSpinner als Komponente (decorative)
    ├─ vue-compat: ✅ h() Render-Function mit isVue2
    └─ webapp:     ✅ 4 Spinner migriert (DsSpinner + LoadingSpinner → OsSpinner)

    BaseCard → OsCard Webapp-Migration: ✅
    ├─ ~30 Webapp-Dateien: <base-card> → <os-card> (lokale Imports)
    ├─ 3 Template-Dateien: #imageColumn/#topMenu Slots → inline Layout mit --columns CSS
    ├─ 16 Spec-Dateien: wrapper.classes('base-card') → wrapper.classes('os-card')
    ├─ 4 Story-Dateien: <base-card> → <os-card> mit Import
    ├─ 12 Cypress E2E-Dateien: .base-card → .os-card Selektoren
    ├─ 2 Cypress-Dateien: .hero-image → .os-card__hero-image
    ├─ BaseCard.vue Komponente gelöscht
    ├─ base-components.js Plugin gelöscht (keine Base*.vue mehr)
    ├─ nuxt.config.js, maintenance config, testSetup.js bereinigt
    ├─ main.scss: .os-card Regeln (title, ds-section, hero-image, --columns Layout)
    ├─ CSS Fixes: Tailwind p-6 Override (!important), outline statt border (highlight),
    │  child selectors → descendant selectors (hero-image content wrapper)
    ├─ ContributionForm: Media-Query Selektoren auf .os-card__content korrigiert
    ├─ ProfileList: .profile-list.os-card Spezifität erhöht (0,3,0 vs 0,2,0)
    └─ 0 <base-card> Template-Nutzungen verbleibend

    DsSpinner/LoadingSpinner → OsSpinner Webapp-Migration: ✅
    ├─ ImageUploader.vue: LoadingSpinner → OsSpinner (size="lg")
    ├─ pages/profile: ds-spinner → os-spinner (size="lg")
    ├─ pages/groups: ds-spinner → os-spinner (size="lg")
    ├─ pages/admin: ds-spinner → os-spinner (size="xl") + ApolloQuery→apollo Option
    ├─ LoadingSpinner Komponente gelöscht
    ├─ ds-space centered → div+padding (Bugfix in 3 Seiten)
    ├─ Admin: ApolloQuery→$apollo.loading (Spinner war wg. SSR-Prefetch unsichtbar)
    └─ infinite-loading: OsSpinner im spinner-Slot (index, profile, groups)

    BaseIcon → OsIcon Webapp-Migration: ✅
    ├─ 131 <base-icon> in 70+ Dateien → <os-icon :icon="...">
    ├─ 82 SVGs in ocelot/icons/svgs/ (inkl. 17 Kategorie-Icons)
    ├─ vite-svg-icon Plugin erweitert (rect, circle, polygon, polyline, ellipse, line)
    ├─ Kategorie-Icons: DB-String → toCamelCase() → ocelotIcons Lookup
    ├─ Jest Mocks: @ocelot-social/ui/ocelot für ocelotIcons in Tests
    ├─ Tests aktualisiert: 911/939 Tests bestanden (3 pre-existing failures)
    └─ 0 base-icon/BaseIcon Referenzen verbleibend

    Tier A ds-* → Plain HTML + CSS: ✅
    ├─ 10 triviale Vue-Wrapper-Komponenten durch HTML-Elemente + CSS-Klassen ersetzt
    ├─ ~450 Nutzungen in ~90 Dateien migriert
    ├─ _ds-compat.scss: Utility-Klassen für Margins (.ds-mb-*, .ds-mt-*, .ds-my-*),
    │  Flex (.ds-flex, .ds-flex-item, .ds-flex-gap-*), Centered (.ds-space-centered)
    ├─ ds-flex/ds-flex-item: JavaScript window.innerWidth → CSS @media Queries
    │  (kein Layout-Shift bei SSR, bessere Performance)
    ├─ system.css bleibt geladen — bestehende CSS-Klassen funktionieren weiter
    ├─ Verbleibend: 8 ds-* Komponenten (Tier B Rest: 1 einfache, Tier C: 6 komplexe → UI-Library)
    └─ 0 Tier-A ds-* Komponenten-Tags verbleibend

    ds-number → OsNumber (UI-Library): ✅
    ├─ OsNumber Komponente: h() Render-Function, requestAnimationFrame Animation, inheritAttrs: false
    ├─ Props: count (required), label (optional), animated (optional)
    ├─ Animation: 1500ms ease-out, watch(count) re-animiert, SSR-safe (onMounted)
    ├─ Styling: tabular-nums + min-width für stabile Breite, --color-text-soft Label-Farbe
    ├─ ds-number + CountTo: 5 Dateien → <os-number> (UserTeaserPopover, TabNavigation, admin, profile, groups)
    ├─ vue-count-to Dependency entfernt, CountTo.vue gelöscht
    ├─ CSS-Variable: --color-text-soft in requiredCssVariables + ocelot-ui-variables.scss
    ├─ 11 Unit-Tests, 5 Stories, 5 Visual Tests + 1 Keyboard Test
    └─ 0 ds-number/CountTo Nutzungen verbleibend

    ds-chip + ds-tag → OsBadge (UI-Library): ✅
    ├─ OsBadge Komponente: CVA-Varianten, h() Render-Function, inheritAttrs: false
    ├─ Props: variant (default/primary/danger), size (sm/md/lg), shape (pill/square)
    ├─ Types: BadgeVariant, BadgeSize, BadgeShape (+ BadgeVariants)
    ├─ ds-chip: 20 Nutzungen in 5 Dateien → <os-badge> (Formzähler + Gruppen-Metadaten)
    ├─ ds-tag: 3 Nutzungen in 3 Dateien → <os-badge shape="square"> (Category, Hashtag)
    ├─ ARIA: role="status" aria-live="polite" auf Form-Zähler (11 Stellen in 2 Dateien)
    ├─ CSS-Variable: --color-default + --color-default-contrast (beide in requiredCssVariables)
    ├─ 18 Unit-Tests, 6 Stories, 5 Visual Tests + 1 Keyboard Test
    └─ 0 ds-chip/ds-tag Nutzungen verbleibend

## Aktueller Stand

Letzte Aktualisierung: 2026-03-28 (Session 35)

Aktuelle Phase: Phase 5 gestartet — Maintenance-App entkoppelt ✅ | Weitere Phase 5 Aufgaben ausstehend

Zuletzt abgeschlossen (Session 35 - Maintenance-App Entkopplung):

- [x] Maintenance-App als eigenständiges Nuxt 4-Projekt unter `maintenance/` aufgesetzt
  - Vue 3 + Nuxt 4 (ssr: false, statisches HTML via `nuxt generate`)
  - `@ocelot-social/ui` als Dependency (OsButton, OsIcon, OsCard)
  - `@nuxtjs/i18n` v10 mit 11 Sprachen (en, de, es, fr, it, nl, pl, pt, ru, sq, uk)
  - `floating-vue` (VDropdown) für LocaleSwitch-Dropdown
  - Tailwind CSS v4 mit `@tailwindcss/vite`
  - Eigene LocaleSwitch-Komponente: OsButton (ghost/circle) + OsIcon (language) + VDropdown
  - Eigene Locale-Dateien unter `maintenance/locales/`
  - Eigene Branding/Design-Tokens unter `maintenance/app/assets/css/`
  - Dockerfile für Docker-Build
  - nginx-Konfiguration für statisches Hosting
  - vitest + @nuxt/test-utils + @vue/test-utils für Tests
  - ESLint mit eslint-config-it4c + @intlify/eslint-plugin-vue-i18n
  - Kein Vuex, kein Apollo, kein v-tooltip — vollständig entkoppelt von Webapp
- [x] packages/ui bleibt i18n-frei (Props-Only-Philosophie bestätigt)

Zuvor abgeschlossen (Session 34 - Architektur-Entscheidungen):

- [x] Styleguide-Ablösung evaluiert: system.css nicht mehr importiert, Styleguide kein Submodul
- [x] Verbleibende ds-CSS-Klassen (~50 Nutzungen) über `_ds-compat.scss` abgedeckt
- [x] Entscheidung: Keine `OsLocaleSwitch` in packages/ui — bricht Props-Only-Philosophie oder ist nur glorifiziertes OsMenu
- [x] Entscheidung: Maintenance-App als eigenständiges Projekt entkoppeln

Zuvor abgeschlossen (Session 33 - ds-radio → native HTML):

- [x] `<ds-radio>` in ReportModal.vue → native `<fieldset>` + `<input type="radio">` + `<label>`
- [x] Accessible Radio-Group: `<fieldset>` mit `<legend>` für Screen-Reader
- [x] CSS: `.report-radio-group`, `.report-radio-option`, `.report-radio-option-label` (in ReportModal `<style>`)
- [x] ReportModal.spec.js: `.ds-radio-option-label` → `input[type="radio"]` Selektor
- [x] Cypress E2E: `.ds-radio-option-label` → `input[type="radio"]` Selektor
- [x] 14/14 Unit-Tests bestanden
- [x] 0 ds-radio Nutzungen verbleibend

Zuvor abgeschlossen (Session 32 - OsNumber: ds-number + CountTo → OsNumber):

- [x] OsNumber Komponente in packages/ui erstellt (h() Render-Function, requestAnimationFrame Animation)
- [x] Props: count (required), label (optional), animated (optional, 1500ms ease-out)
- [x] Animation: requestAnimationFrame-Loop, watch(count) re-animiert von oldVal→newVal
- [x] Stabile Breite: `tabular-nums` + `min-width: Nch` basierend auf Zielwert-Ziffernanzahl
- [x] CSS-Variable `--color-text-soft` in tailwind.preset.ts (requiredCssVariables), Storybook-Theme, ocelot-ui-variables.scss
- [x] 5 Webapp-Dateien migriert: UserTeaserPopover (statisch), TabNavigation (animated), admin/index (animated), profile/_slug (animated), groups/_slug (animated)
- [x] CountTo.vue gelöscht, `vue-count-to` Dependency aus package.json entfernt
- [x] `followedByCountStartValue` / `membersCountStartValue` Pattern entfernt (OsNumber watch-basiert)
- [x] ds-number CSS aus `_ds-compat.scss` entfernt
- [x] Admin-Dashboard: `.os-number-label { text-transform: uppercase }` per CSS (kein neuer Prop)
- [x] Test-Fixes: Fehlende Count-Properties in Mock-Daten (followedByCount, contributionsCount, membersCount etc.)
- [x] 11 Unit-Tests, 5 Stories, 5 Visual + A11y Tests

Zuvor abgeschlossen (Session 31 - ds-table → Plain HTML):

- [x] ds-table (7 Nutzungen) → native `<table>` + CSS-Klassen (kein OsTable nötig)
- [x] Table-CSS in `_ds-compat.scss`: .ds-table-wrap, .ds-table, .ds-table-col, .ds-table-head-col, .ds-table-bordered, .ds-table-condensed, Alignment-Klassen
- [x] `fields()` / `tableFields()` Computed Properties aus allen 7 Dateien entfernt (Labels direkt in `<th>`)
- [x] Alle 16 Tests bestanden (3 Test-Suites: admin/users Snapshots aktualisiert, FiledReportsTable ✅, ReportsTable ✅)

Zuvor abgeschlossen (Session 30 - OsBadge Code-Review Fixes):

- [x] `--color-default-contrast` zu `requiredCssVariables` in tailwind.preset.ts hinzugefügt
- [x] Doppelte `--color-default`-Deklaration in ocelot-ui-variables.scss entfernt (softest → softer konsolidiert)
- [x] Redundante Ternär-Ausdrücke entfernt: GroupTeaser.vue + groups/_slug.vue (`group ? group.about : ''` → `group.about`)
- [x] `BadgeVariant` Typ hinzugefügt und in OsBadge.vue verwendet (statt `NonNullable<BadgeVariants['variant']>`)
- [x] Exports erweitert: BadgeVariant in index.ts + components/index.ts
- [x] GroupForm.vue: `float: right` → `display: flex; flex-direction: column; align-self: flex-end` (konsistentes Flexbox-Layout)
- [x] PlaygroundArgs in Stories: `string` → `BadgeVariant | BadgeSize | BadgeShape` (typsichere Story-Args)
- [x] WithIcon-Story: Inline-Style `style="margin-right: 4px"` → Tailwind `class="mr-1"`
- [x] ARIA Live Regions: `role="status" aria-live="polite"` auf 10 Form-Zähler + `role="alert" aria-live="assertive"` auf 1 Fehler-Badge
- [x] `live` Prop entfernt — Standard-ARIA-Attribute werden direkt durchgereicht (attrs)
- [x] KATALOG.md: Übersichtstabelle, Tier B Status, Komponenten-Tabelle, Zusammenfassungen aktualisiert

Zuvor abgeschlossen (Session 29 - OsBadge: ds-chip + ds-tag Migration):

- [x] OsBadge Komponente in packages/ui erstellt (CVA-Varianten, h() Render-Function)
- [x] Props: variant (default/primary/danger), size (sm/md/lg), shape (pill/square)
- [x] ds-chip → OsBadge: 20 Nutzungen in 5 Webapp-Dateien (GroupTeaser, GroupMember, GroupForm, ContributionForm, groups/_slug)
- [x] ds-tag → OsBadge: 3 Nutzungen in 3 Webapp-Dateien (Category, Hashtag, PostTeaser CSS)
- [x] CSS-Variable --color-default für neutralen Badge-Hintergrund
- [x] Unit-Tests (16), Stories (6), Visual Tests (5+1 Keyboard), 100% Coverage
- [x] Exports: OsBadge, badgeVariants, BadgeShape, BadgeSize, BadgeVariants

Zuvor abgeschlossen (Session 27 - Tier A: ds- Komponenten → Plain HTML):*

- [x] `_ds-compat.scss` erstellt (Utility-Klassen für Margins, Flex, Centered)
- [x] `ds-section` → `<section class="ds-section">` (5 Dateien)
- [x] `ds-placeholder` → `<div class="ds-placeholder">` (4 Dateien)
- [x] `ds-tag` → `<span class="ds-tag">` (2 Dateien)
- [x] `ds-list` → `<ul class="ds-list">` (4 Dateien)
- [x] `ds-list-item` → `<li class="ds-list-item">` (8 Dateien)
- [x] `ds-container` → `<div class="ds-container ds-container-{width}">` (14 Dateien, 12 Dateien)
- [x] `ds-heading` → `<h1-h4 class="ds-heading ds-heading-h{n}">` (31 Nutzungen, 25 Dateien)
- [x] `ds-text` → `<p/div class="ds-text ds-text-{color} ds-text-size-{size}">` (80 Nutzungen, 42 Dateien)
- [x] `ds-space` → `<div class="ds-mb-{size}">` / `<div class="ds-my-{size}">` (139 Nutzungen, 55+ Dateien)
- [x] `ds-flex` / `ds-flex-item` → Plain HTML + CSS Media Queries (103 Nutzungen, 29 Dateien)
- [x] Responsive Layouts: JavaScript `window.innerWidth` → CSS `@media` Queries (kein Layout-Shift)
- [x] Bugfix: `<p>` mit Block-Level-Kindern → `<div>` (DateTimeRange.vue, verify.vue)
- [x] Test-Fix: Empty.spec.js `attributes().margin` → `classes().toContain('ds-my-xxx-small')`
- [x] 0 Tier-A `ds-*` Komponenten-Tags verbleibend

Verbleibende ds- Komponenten: ✅ ALLE MIGRIERT (0 ds- Tags in Webapp)**

- ✅ ds-modal (7→OsModal), ds-input (23→OcelotInput), ds-select (3→OcelotSelect), ds-menu/ds-menu-item (17→OsMenu/OsMenuItem)
- ✅ ds-form (18 Dateien) → formValidation Mixin (async-validator), vuelidate entfernt

Zuvor abgeschlossen (Session 26 - CodeRabbit Review Fixes):

- [x] Cypress: `.os-card .title` → `.os-card > .title` (Kind-Kombinator statt Nachfahren)
- [x] OsCard.spec.ts: `compareDocumentPosition()` Bitmask-Assertion `!== 0` statt `.toBe(true)`
- [x] Maintenance-App: `@ocelot-social/ui` Abhängigkeit + `./nuxt.config.js` Import als pre-existing (kein Scope) bewertet

Zuvor abgeschlossen (Session 25 - BaseCard → OsCard Webapp-Migration):

- [x] ~30 Webapp-Dateien: `<base-card>` → `<os-card>` mit lokalen Imports
- [x] 3 Template-Dateien mit #imageColumn/#topMenu Slots → inline Layout (LoginForm, RegistrationSlider, password-reset)
- [x] CSS: `.os-card.--columns` Layout in main.scss (flex, image-column, content-column, top-menu, responsive)
- [x] 16 Spec-Dateien: `wrapper.classes('base-card')` → `wrapper.classes('os-card')`
- [x] 4 Story-Dateien: `<base-card>` → `<os-card>` mit OsCard-Import
- [x] 12 Cypress E2E Step-Definitions: `.base-card` → `.os-card` Selektoren
- [x] 2 Cypress-Dateien: `.hero-image` → `.os-card__hero-image`
- [x] BaseCard.vue Komponente gelöscht
- [x] `base-components.js` Plugin gelöscht (keine Base*.vue Komponenten mehr)
- [x] Plugin-Referenzen entfernt: nuxt.config.js, nuxt.config.maintenance.js, testSetup.js
- [x] main.scss bereinigt: `.base-card > .ds-section` entfernt, `.os-card` Regeln hinzugefügt
- [x] CSS-Fixes: Tailwind `p-6` Override (`!important`), `outline` statt `border` (highlight), child → descendant selectors
- [x] ContributionForm: Media-Query Selektoren auf `.os-card__content > .buttons-footer` korrigiert
- [x] ProfileList: Spezifität `.profile-list.os-card` erhöht (0,3,0 vs 0,2,0)
- [x] OsCard highlight Tests: `border` → `outline-1` (twMerge), Testnamen aktualisiert
- [x] Kleinere Verbesserungen: SocialMedia Props typisiert, LoginForm querySelector statt fragiler DOM-Traversierung, redundante `<client-only>` entfernt, NotificationsTable optional chaining
- [x] `hasBaseCard` Property verbleibt in 4 Dateien (rein semantisch, kein Komponentenbezug)

Zuvor abgeschlossen (Session 24 - OsSpinner Webapp-Migration + Refactoring):

- [x] OsButton refactored: nutzt `h(OsSpinner, { 'aria-hidden': 'true' })` statt Inline-SVG
- [x] OsSpinner: Decorative-Modus (`aria-hidden="true"` unterdrückt role/aria-label)
- [x] `ButtonSize` Type exportiert (sm/md/lg/xl), `types.d.ts` Kommentar aktualisiert
- [x] `createSpinnerSvg.ts` wieder in OsSpinner.vue inlined (nur noch 1 Nutzer)
- [x] Webapp-Migration: 4 Spinner ersetzt (ImageUploader, profile, groups, admin)
- [x] LoadingSpinner Komponente gelöscht (ersetzt durch OsSpinner)
- [x] `<ds-space centered>` → `<div style="...">` Bugfix in 3 Seiten
- [x] Admin-Seite: `<ApolloQuery>` → `apollo`-Option + `$apollo.loading` (Spinner war wg. SSR-Prefetch unsichtbar)
- [x] `filterStatistics()` mutiert nicht mehr Originalobjekt (Destructuring statt `delete`)
- [x] `infinite-loading` Spinner-Slot: OsSpinner in allen 3 Nutzungen (index, profile, groups)
- [x] Einheitliches Spinner-Design: vue-infinite-loading Default-Spinner → OsSpinner

Zuvor abgeschlossen (Session 23 - OsSpinner Komponente):

- [x] OsSpinner Komponente implementiert (size prop, currentColor, role="status", aria-label)
- [x] Vue 2/3 kompatibel via `h()` Render-Function mit `isVue2`
- [x] Storybook Stories: Playground, AllSizes, InheritColor, InlineWithText
- [x] Unit Tests: 23 Tests (rendering, size, accessibility, decorative mode, css, keyboard)
- [x] Visual Tests: 4 Tests (all-sizes, inherit-color, inline-text, keyboard a11y)
- [x] Accessibility: `role="status"`, `aria-label="Loading"` (customizable), axe-core checks
- [x] 100% Test-Coverage (Statements, Branches, Functions, Lines)
- [x] Completeness Check bestanden
- [x] OsButton Visual Tests: 19/19 bestanden (kein Regression durch Refactoring)

Zuvor abgeschlossen (Session 22 - BaseIcon → OsIcon Webapp-Migration):

- [x] 131 `<base-icon>` Nutzungen in 70+ Dateien → `<os-icon :icon="icons.xxx">` migriert
- [x] 82 Ocelot-Icons in `packages/ui/src/ocelot/icons/svgs/` (von 1 auf 82)
- [x] 17 Kategorie-Icons aus Webapp kopiert (networking, energy, psyche, movement, finance, child, mobility, shopping-cart, peace, politics, nature, science, health, media, spirituality, culture, miscellaneous)
- [x] vite-svg-icon Plugin erweitert: unterstützt `<rect>`, `<circle>`, `<polygon>`, `<polyline>`, `<ellipse>`, `<line>` (war path-only)
- [x] Alle neuen SVGs auf Single-Line minifiziert (Multiline brach JS-String-Literale)
- [x] Kategorie-Icons: DB-String → `toCamelCase()` → `ocelotIcons[key]` Lookup (Category/index.vue, CategoriesFilter.vue, CategoriesSelect.vue, admin/categories.vue)
- [x] `created() { this.icons = ocelotIcons }` Pattern in allen Komponenten (non-reactive)
- [x] MenuLegend.vue: `legendItems` von `data()` → `computed` (data() läuft vor created(), this.icons undefined)
- [x] HeaderMenu.vue: Map-Button Icon-Größe korrigiert (`size="xl"` + negative Margin)
- [x] ShowPassword.vue: `:data-test="iconName"` entfernt (Icon ist jetzt Render-Function, kein String)
- [x] Jest-Tests aktualisiert: OsIcon + ocelotIcons statt BaseIcon + String-Namen
  - Category/index.spec.js, ProfileAvatar.spec.js, CounterIcon.spec.js, ReportRow.spec.js
  - ActionButton.spec.js, ComponentSlider.spec.js, ShowPassword.spec.js, LoginForm.spec.js
- [x] 8 stale Snapshot-Dateien gelöscht
- [x] Jest Mock: `test/__mocks__/@ocelot-social/ui/ocelot.js` für ocelotIcons in Tests
- [x] CSS: `.base-icon` → `.os-icon` in main.scss und Category/index.vue
- [x] 0 `base-icon`/`BaseIcon` Referenzen verbleibend in gesamter Webapp
- [x] 911/939 Tests bestanden (3 pre-existing Jest worker crashes)

Zuvor abgeschlossen (Session 21 - OsIcon Komponente, System-Icons, Ocelot-Umbenennung):

- [x] OsIcon Komponente implementiert (name, icon, size Props; Vue 2/3 via vue-demi h())
- [x] System-Icons: check, close, plus (SVG, viewBox 0 0 32 32, stroke-basiert)
- [x] Custom vite-svg-icon Plugin: SVG → Vue Render-Function via `?icon` Query
- [x] Ocelot-Icons: separates Entry-Point (ocelot.mjs) mit dynamischem Loading via import.meta.glob
- [x] `src/webapp/` → `src/ocelot/` umbenannt (konsistentes Naming)
- [x] 100% Test-Coverage für OsIcon

Zuvor abgeschlossen (Session 20 - `as`-Prop + nuxt-link Migration):

- [x] OsButton: `as` Prop implementiert (polymorphe Komponente: `button`, `a`, `nuxt-link`, `router-link`, Custom-Komponenten)
- [x] Naming-Konvention: `tag` → `as` (moderner Standard: Headless UI, Radix Vue, Chakra UI, PrimeVue)
- [x] `disabled`/`type`/`loading` nur bei `as="button"` (Links haben kein natives `disabled`-Attribut)
- [x] Stories: `Polymorphic` Story + Playground `as`-Selektor (button/a)
- [x] Visual Test: `polymorphic` Screenshot + a11y-Check
- [x] 15 `<nuxt-link>`/`<a>`-Wrapper in 15 Webapp-Dateien → `as="nuxt-link"` / `as="a"` migriert:
  - GroupButton.vue, CtaUnblockAuthor.vue, terms-and-conditions-confirm.vue
  - CustomButton.vue (v-if/v-else → computed `linkTag`/`linkProps` konsolidiert)
  - groups/index.vue, GroupForm.vue, admin/users/index.vue
  - pages/index.vue (CSS `button.post-add-button-*` → `.post-add-button-*`)
  - profile/_id/_slug.vue (v-if auf ds-grid-item, symmetrisches Padding `$space-x-small`)
  - groups/_id/_slug.vue, MapButton.vue
  - ChatNotificationMenu.vue, Chat.vue, UserTeaserPopover.vue
  - NotificationMenu.vue (3 Instanzen, 2 zu einem Button konsolidiert via counter-icon)
- [x] Verifiziert: 0 verbleibende `<nuxt-link>`/`<a>`-Wrapper um `<os-button>` in Webapp

Zuvor erledigt (auf master gemergt):

- [x] `os-button` CSS-Klasse auf Button-Element für Branding-Kompatibilität (#9211)
- [x] eslint-config-it4c v0.11.2 Update: Flat Config, path alias #src, CSS-Linting (#9233)
- [x] Release @ocelot-social/ui v0.0.2
- [x] Release v3.14.1

Abgeschlossene Phasen:

- [x] Phase 0: Analyse (177 Komponenten katalogisiert)
- [x] Phase 1: Vue 2.7 Upgrade (2.6.14 → 2.7.16, 979 Tests ✅)
- [x] Phase 2: Projekt-Setup (Vite, vue-demi, Tailwind v4, CVA, Storybook 10, CI/CD, 100% Coverage)
- [x] Phase 3: Webapp-Integration — 133 os-button in 79 Dateien, 0 base-button/ds-button verbleibend

Zuvor abgeschlossen (Sessions 11-19 — Details im Arbeitsprotokoll §12):

- [x] Session 19: CodeRabbit Review Cleanup, ~30 Bugfixes + A11y-Verbesserungen
- [x] Session 18: CodeRabbit Review data-test Selektoren, ~25 A11y aria-labels, OsButton Refactoring
- [x] Session 16: Letzte ds-button Migration, Bugfixes, data-variant Attribut
- [x] Session 15: Milestone 4c — 59 Buttons migriert, 0 base-button verbleibend
- [x] Session 14: Loading Prop, Circle Prop, Spinner-Architektur, Code-Optimierung
- [x] Session 13: Icon-Slot, Storybook Playground, 6 Icon-Buttons migriert
- [x] Session 12: CSS-Linting, CI-Optimierung, Code-Review Fixes
- [x] Session 11: Wasserfarben-Farbschema, Stories konsolidiert, Keyboard A11y

Nächste Schritte:

- [x] Maintenance-App entkoppelt ✅ (eigenständiges Nuxt 4-Projekt unter `maintenance/`)
  - [x] Eigene LocaleSwitch: OsButton + OsIcon + floating-vue VDropdown (kein Vuex/Apollo/v-tooltip)
  - [x] Eigenständige i18n-Konfiguration (@nuxtjs/i18n v10, 11 Sprachen)
  - [x] Abhängigkeit nur auf @ocelot-social/ui + floating-vue + Design-Tokens
  - [x] Eigener Build (Nuxt 4 generate → statisches HTML + nginx + Docker)
- [ ] Verbleibende ds-CSS-Klassen ablösen (`_ds-compat.scss` → eigene Utility-Klassen oder Tailwind)
- [ ] Weitere Tier 2 Komponenten bei Bedarf (OsDropdown, OsAvatar)
- [ ] Browser-Fehler untersuchen: `TypeError: Cannot read properties of undefined (reading 'heartO')` (ocelotIcons undefined im Browser trotz korrekter Webpack-Aliase)

Manuelle Setup-Aufgaben (außerhalb Code):

- [ ] `NPM_TOKEN` als GitHub Secret einrichten (für npm publish in ui-release.yml)
  - npm Token erstellen: https://www.npmjs.com/settings/ocelot-social/tokens
  - GitHub Secret: Repository → Settings → Secrets → Actions → New secret
- [ ] Storybook auf externem Host deployen (via Webhook)
  - Server einrichten für Storybook-Hosting
  - Webhook-Endpoint erstellen (zieht + baut bei Release)
  - GitHub Webhook konfigurieren (trigger bei Release)

## Meilensteine

### Phase 0: Analyse & Katalogisierung ✅

- [x] Vollständige Katalogisierung Webapp-Komponenten (139 Komponenten)
- [x] Vollständige Katalogisierung Styleguide-Komponenten (38 Komponenten)
- [x] Duplikate identifizieren und dokumentieren (5 direkte + 3 Familien)
- [x] Inkonsistenzen und Probleme erfassen (Button/Modal/Menu analysiert)
- [x] Konsolidierungsplan erstellen (Token-Liste)
- [x] Priorisierung der zu migrierenden Komponenten (15 Komponenten in 4 Tiers)

### Phase 1: Vue 2.7 Upgrade ✅

- [x] Vue 2.6 → Vue 2.7 Upgrade in Webapp (2.6.14 → 2.7.16)
- [x] Abhängigkeiten aktualisieren:
  - [x] vue-template-compiler entfernt (in Vue 2.7 eingebaut)
  - [x] @vue/composition-api entfernt (in Vue 2.7 eingebaut)
  - [x] @nuxtjs/composition-api entfernt (nicht mehr nötig)
  - [x] vue-server-renderer auf 2.7.16 aktualisiert
- [x] Tests durchführen: 157 Suites, 979 passed, 87 Snapshots ✅
- [x] Regressionstests (`yarn dev` und manuelle Prüfung) ✅

### Phase 2: Projekt-Setup ✅

- [x] Vite + Vue 3 Projekt initialisieren
- [x] vue-demi einrichten für Vue 2 Kompatibilität
- [x] Tailwind CSS einrichten (v4 mit @tailwindcss/vite)
- [x] Dual-Build konfigurieren (Tailwind Preset + vorkompilierte CSS)
- [x] CSS Custom Properties Token-System aufsetzen (requiredCssVariables + validateCssVariables)
- [x] Dark Mode Grundstruktur (via Tailwind `dark:` Prefix, dokumentiert)
- [x] Storybook für Dokumentation einrichten
- [x] Vitest konfigurieren
- [x] eslint-config-it4c einrichten (v0.8.0: TypeScript, Vue 3, Vitest, Prettier)
- [x] npm Package-Struktur (@ocelot-social/ui) mit korrekten exports
- [x] Vue 2/3 Kompatibilitätstests (via Example Apps)
- [x] GitHub Workflows einrichten (ui-lint.yml, ui-test.yml, ui-build.yml, ui-compatibility.yml)
- [x] Docker Setup (Dockerfile, docker-compose, ui-docker.yml Workflow)
- [x] Visual Regression Tests einrichten (Playwright, colocated mit Komponenten)
- [x] Accessibility Tests in Visual Tests integriert (@axe-core/playwright)
- [x] Keyboard Accessibility Tests (describe('keyboard accessibility'))
- [x] ESLint Plugins: vuejs-accessibility, playwright, storybook, jsdoc, @eslint/css
- [x] Bundle Size Check einrichten (size-limit, ui-size.yml)
- [x] Package-Validierung einrichten (publint, arethetypeswrong)
- [x] Example Apps erstellen (vue3-tailwind, vue3-css, vue2-tailwind, vue2-css)
- [x] Kompatibilitätstest-Workflow einrichten (4er-Matrix, siehe §18)
- [x] release-please Manifest-Konfiguration
- [x] npm Publish Workflow (ui-release.yml)
- [x] Storybook Build Workflow (ui-storybook.yml)
- [x] LICENSE Datei (Apache 2.0)
- [x] README.md Grundgerüst (Installation, Quick Start, Struktur)
- [x] CONTRIBUTING.md
- [x] Completeness Check Script (Story, Visual+checkA11y, Keyboard, Varianten)

### Phase 3: Webapp-Integration (Validierung)

Ziel: OsButton in der Webapp einbinden, ohne visuelle oder funktionale Änderungen.

Ansatz: Integration First - Library einbinden, dann schrittweise OsButton ersetzen, beginnend mit einfachsten Stellen.

Milestone 1: Library-Einbindung ✅

- [x] @ocelot-social/ui in Webapp installieren (yarn link + Webpack-Alias)
- [x] vue-demi zur Webapp hinzugefügt (für Vue 2.7 Kompatibilität)
- [x] Webpack-Alias für vue-demi (nutzt Webapp's Vue 2.7)
- [x] Webpack-Alias für @ocelot-social/ui$ und style.css$
- [x] CSS Custom Properties in Webapp definieren (ocelot-ui-variables.scss)
- [x] CSS-Reihenfolge angepasst (UI-Library nach Styleguide)
- [x] Import-Pfade testen
- [x] Docker Build Stage für UI-Library (Dockerfile + Dockerfile.maintenance)
- [x] Relativer Pfad für CI-Kompatibilität (file:../packages/ui)
- [x] Jest Mock für @ocelot-social/ui (test/mocks/@ocelot-social/ui.js)

Milestone 2: Erste Integration (Minimaler Aufwand) ✅

- [x] OsButton mit isVue2 Render-Funktion (Vue 2/3 kompatibel)
- [x] Button-Variants an ds-button angepasst (font-semibold, rounded, box-shadow)
- [x] OsButton in UserTeaserPopover.vue eingesetzt (`variant="primary"`)
- [x] Manueller visueller Vergleich ✅
- [x] Webapp-Tests bestehen ✅ (979 Tests, jest moduleNameMapper für vue-demi)

Milestone 3: Schrittweise Erweiterung ✅

- [x] GroupForm.vue Cancel-Button migriert
- [x] OsButton attrs/listeners Forwarding (Vue 2 $listeners via getCurrentInstance)
- [x] 14 weitere Buttons migriert (alle ohne icon/circle/loading)

Milestone 4a: Weitere Buttons migrieren (14 ohne neue Props) ✅

- [x] Modal Cancel-Buttons (DisableModal, DeleteUserModal, ReleaseModal)
- [x] Form Cancel/Submit-Buttons (ContributionForm, EnterNonce, MySomethingList)
- [x] ImageUploader.vue (2× Crop-Buttons)
- [x] Page-Buttons (donations, badges, notifications/index, profile Unblock/Unmute)
- [x] ReportRow.vue More-Details-Button

Milestone 4b: OsButton Props erweitern ✅

- [x] `icon` Slot implementiert (slot-basiert, icon-system-agnostisch) ✅
- [x] `circle` Prop implementiert (rounded-full, größenabhängige Breiten) ✅
- [x] `loading` Prop mit animiertem SVG-Spinner implementiert ✅

Milestone 4c: Buttons mit icon/circle/loading migrieren ✅ ABGESCHLOSSEN

Button-Komponenten (Wrapper):

- [x] Button/JoinLeaveButton.vue (icon, loading) ✅
- [x] Button/FollowButton.vue (icon, loading) ✅
- [x] LoginButton/LoginButton.vue (icon, circle) ✅
- [x] InviteButton/InviteButton.vue (icon, circle) ✅
- [x] EmotionButton/EmotionButton.vue (circle) ✅
- [x] CustomButton/CustomButton.vue (2× circle) ✅
- [x] LabeledButton/LabeledButton.vue (icon, circle) ✅

Navigation & Menus:

- [x] ContentMenu/ContentMenu.vue (icon, circle) ✅
- [x] ContentMenu/GroupContentMenu.vue (icon, circle) ✅
- [x] ChatNotificationMenu.vue (circle) ✅
- [x] NotificationMenu.vue (3× icon, circle) ✅
- [x] HeaderMenu/HeaderMenu.vue (icon, circle) ✅
- [x] Map/MapButton.vue (circle) ✅

Editor:

- [x] Editor/MenuBar.vue (~11× icon, circle) ✅
- [x] Editor/MenuLegend.vue (2× icon) ✅

Filter & Input:

- [x] HashtagsFilter.vue (icon, circle) ✅
- [x] CategoriesSelect.vue (icon) ✅
- [x] SearchableInput.vue (icon, circle) ✅
- [x] Select/LocationSelect.vue (icon) ✅
- [x] PaginationButtons.vue (2× icon, circle) ✅

Chat:

- [x] Chat/Chat.vue (2× icon, circle) ✅
- [x] Chat/AddChatRoomByUserSearch.vue (icon, circle) ✅

Forms & Auth:

- [x] LoginForm/LoginForm.vue (icon, loading) ✅
- [x] PasswordReset/Request.vue (loading) ✅
- [x] PasswordReset/ChangePassword.vue (loading) ✅
- [x] Password/Change.vue (loading) ✅
- [x] ContributionForm.vue Submit (icon, loading) ✅
- [x] CommentForm/CommentForm.vue (loading) ✅

Modals:

- [x] Modal/ConfirmModal.vue (2× icon, loading) ✅
- [x] Modal/ReportModal.vue (2× icon, loading) ✅
- [x] Modal/DisableModal.vue Confirm (icon) ✅
- [x] Modal/DeleteUserModal.vue Confirm (icon) ✅
- [x] Modal/ReleaseModal.vue Confirm (icon) ✅

Features:

- [x] ComponentSlider.vue (2× icon) ✅
- [x] MySomethingList.vue (3× icon, circle) ✅
- [x] CreateInvitation.vue (icon, circle) ✅
- [x] Invitation.vue (2× icon, circle) ✅
- [x] ProfileList.vue (loading) ✅
- [x] ReportRow.vue Confirm (icon) ✅
- [x] ImageUploader.vue Delete/Cancel (2× icon, circle) ✅
- [x] CommentCard.vue Reply (icon, circle) ✅
- [x] EmbedComponent.vue Close (icon, circle) ✅
- [x] CtaUnblockAuthor.vue (icon) ✅
- [x] data-download.vue (icon, loading) ✅
- [x] ActionButton.vue (icon, circle) ✅
- [x] DeleteData.vue (icon) ✅
- [x] GroupButton.vue (icon, circle) ✅

Filter-Menüs:

- [x] FilterMenu/FilterMenu.vue (icon) ✅
- [x] FilterMenu/HeaderButton.vue (2× icon) ✅
- [x] FilterMenu/CategoriesFilter.vue (2× icon) ✅
- [x] FilterMenu/OrderByFilter.vue (2×) ✅
- [x] FilterMenu/EventsByFilter.vue (2×) ✅
- [x] FilterMenu/FollowingFilter.vue (3×) ✅

Pages:

- [x] pages/index.vue (2× icon, circle) ✅
- [x] pages/groups/index.vue (icon, circle) ✅
- [x] pages/groups/_id/_slug.vue (3× icon, circle) ✅
- [x] pages/admin/users/index.vue (2× icon, circle) ✅
- [x] pages/settings/index.vue (icon) ✅
- [x] pages/settings/blocked-users.vue (icon, circle) ✅
- [x] pages/settings/muted-users.vue (icon, circle) ✅
- [x] pages/settings/data-download.vue (icon) ✅
- [x] pages/settings/my-email-address/index.vue (icon) ✅
- [x] pages/settings/my-email-address/enter-nonce.vue (icon) ✅
- [x] pages/profile/_id/_slug.vue (icon, circle) ✅
- [x] pages/post/_id/_slug/index.vue (icon, circle) ✅

Milestone 5: Validierung & Dokumentation ✅

- [x] Keine visuellen Änderungen bestätigt (16/16 Buttons validiert)
- [x] Keine funktionalen Änderungen bestätigt
- [x] Disabled-Styles korrigiert (hover/active-Override, Border-Fix)
- [ ] Webapp-Tests bestehen weiterhin (TODO: Snapshots aktualisieren)
- [ ] Erkenntnisse in KATALOG.md dokumentiert

Einsatzstellen-Übersicht:

Kategorie | Buttons | Status
--- | --- | ---
✅ Migriert (gesamt) | 133 | 79 Dateien
⬜ `<base-button>` verbleibend | 0 | Nur BaseButton.vue Definition + Test-Dateien
⬜ `<ds-button>` verbleibend | 0 | Alle ersetzt
Gesamt | 133 | 100% erledigt ✅

Details siehe KATALOG.md (vollständige Tracking-Tabellen)

Erfolgskriterien:

Kriterium | Prüfung
--- | ---
Visuell identisch | Manueller Screenshot-Vergleich
Funktional identisch | Click, Disabled funktionieren
Keine Regression | Webapp Unit-Tests bestehen

Visuelle Validierung (OsButton vs Original):

Jeder migrierte Button muss manuell geprüft werden: Normal, Hover, Focus, Active, Disabled.

Datei | Button | Props | Validiert
--- | --- | --- | ---
`components/Group/GroupForm.vue` | Cancel | `default` | ✅
`components/Group/GroupMember.vue` | Remove Member | `appearance="outline" variant="primary" size="sm"` | ✅
`components/CommentCard/CommentCard.vue` | Show more/less | `appearance="ghost" variant="primary" size="sm"` | ✅
`components/UserTeaser/UserTeaserPopover.vue` | Open Profile | `variant="primary"` | ✅
`components/DonationInfo/DonationInfo.vue` | Donate Now | `size="sm" variant="primary"` | ✅
`components/Map/MapStylesButtons.vue` | Map Styles | `:appearance` dynamisch + custom CSS | ✅
`components/Embed/EmbedComponent.vue` | Cancel | `appearance="outline" variant="danger"` + custom CSS | ✅
`components/Embed/EmbedComponent.vue` | Play Now | `variant="primary"` + custom CSS | ✅
`pages/terms-and-conditions-confirm.vue` | Read T&C | `appearance="outline" variant="primary"` | ✅
`pages/terms-and-conditions-confirm.vue` | Save | `variant="primary"` + disabled | ✅
`pages/settings/privacy.vue` | Save | `variant="primary"` + disabled | ✅
`pages/settings/notifications.vue` | Check All | `appearance="outline" variant="primary"` + disabled | ✅
`pages/settings/notifications.vue` | Uncheck All | `appearance="outline" variant="primary"` + disabled | ✅
`pages/settings/notifications.vue` | Save | `variant="primary"` + disabled | ✅
`pages/settings/embeds.vue` | Allow All | `appearance="outline" variant="primary"` + disabled | ✅
`pages/settings/embeds.vue` | Deny All | `variant="primary"` + disabled | ✅

Validierung abgeschlossen: 16/16 (100%) ✅

Nach Abschluss aller Validierungen:

- [ ] Gesamt-Regressionstest durchführen
- [ ] Alle Unit-Tests bestehen
- [ ] Dokumentation aktualisieren

### Phase 4: Komponenten-Migration

Tier 1: Kern-Komponenten (UI-Library) ✅

- [x] OsIcon (vereint DsIcon + BaseIcon) ✅ System-Icons + vite-svg-icon Plugin
- [x] OsSpinner (vereint DsSpinner + LoadingSpinner) ✅ OsButton nutzt OsSpinner als Komponente
- [x] OsSpinner Webapp-Migration ✅ 4 Spinner migriert, LoadingSpinner gelöscht
- [x] OsButton (vereint DsButton + BaseButton) ✅ 133 Buttons in 79 Dateien
- [x] OsCard (vereint DsCard + BaseCard) ✅ ~30 Dateien, BaseCard gelöscht

Tier A: Triviale ds- Wrapper → Plain HTML + CSS* ✅

- [x] _ds-compat.scss Utility-Klassen (Margins, Flex, Centered)
- [x] ds-section, ds-placeholder, ds-tag, ds-list, ds-list-item → HTML + CSS-Klassen
- [x] ds-container, ds-heading, ds-text → HTML + CSS-Klassen
- [x] ds-space → div + Margin-Utility-Klassen (139 Nutzungen)
- [x] ds-flex / ds-flex-item → HTML + CSS @media Queries (103 Nutzungen)

Tier B: Einfache ds- → Plain HTML / UI-Library*

- [x] ds-chip (5 Dateien) → OsBadge (UI-Library) ✅
- [x] ds-tag (3 Dateien) → OsBadge shape="square" (UI-Library) ✅
- [x] ds-number (5 Dateien) → OsNumber (UI-Library) ✅ + CountTo.vue gelöscht, vue-count-to entfernt
- [x] ds-grid / ds-grid-item (10 Dateien) → CSS Grid ✅
- [x] ds-radio (1 Datei) → native `<input type="radio">` ✅

Tier 2: Layout & Feedback (UI-Library)

- [x] OsModal (Basis: DsModal → h() Render-Function, Vue 2/3 Compat, Focus-Trap, Scroll-Lock, A11y) ✅
- [ ] OsDropdown (Basis: Webapp Dropdown)
- [ ] OsAvatar (vereint DsAvatar + ProfileAvatar)
- [x] ds-input → OcelotInput (23 Dateien, Webapp-Komponente mit lokalen Imports, formValidation-kompatibel) ✅

Tier 3: Navigation (UI-Library)

- [x] OsMenu + OsMenuItem (packages/ui, h() Render, vue-demi, provide/inject, dropdown Prop, eigene CSS in index.css, 17 Nutzungen in 11 Dateien) ✅

Tier 4: Spezial-Komponenten

- [x] ds-select → OcelotSelect (3 Dateien, Webapp-Komponente, click-outside inline, DsChip→OsBadge, DsSpinner→OsSpinner) ✅
- [x] ds-table (7 Dateien) → Plain HTML `<table>` + CSS-Klassen ✅ (kein OsTable nötig)
- [x] ds-form → formValidation Mixin (async-validator), vuelidate entfernt ✅

Infrastruktur

- [x] System-Icons einrichten ✅ vite-svg-icon Plugin, 3 System-Icons, Ocelot-Icons Entry-Point
- [x] BaseIcon → OsIcon Webapp-Migration ✅ 131 Nutzungen, 82 Ocelot-Icons, 0 BaseIcon verbleibend
- [ ] CI docs-check Workflow (JSDoc-Coverage, README-Aktualität)

> Hinweis: ds-heading, ds-text, ds-tag wurden zu Plain HTML migriert (Tier A). OsHeading/OsText/OsTag als UI-Library-Komponenten sind daher nicht mehr geplant.

### Phase 5: Finalisierung & Entkopplung

- [x] Maintenance-App als eigenständiges Projekt ✅ (Erste Validierung der Library-Unabhängigkeit)
  - [x] Eigene LocaleSwitch (OsButton + OsIcon + floating-vue VDropdown, kein Vuex/Apollo)
  - [x] Eigenständiger Build ohne Webapp-Abhängigkeiten (Nuxt 4 + Docker + nginx)
  - [x] Nur @ocelot-social/ui + @nuxtjs/i18n + floating-vue + Design-Tokens
- [ ] Alle Komponenten migriert und getestet
- [ ] Alte Komponenten aus Vue 2 Projekt entfernt
- [ ] Build als npm Library verifiziert
- [ ] README.md finalisieren (alle Sektionen vollständig)
- [ ] ARCHITECTURE.md erstellen (aus PROJEKT.md §2, §4, §5, §11, §15, §16)
- [ ] PROJEKT.md und KATALOG.md archivieren (docs/archive/)
- [ ] Dokumentation vollständig und CI-geprüft

# VISION

## 1. Projektziel & Vision

Kurzbeschreibung: Neue Vue 3 Komponentenbibliothek aufbauen, die später die Vue 2 Komponenten in der Webapp ersetzen soll.

Hintergrund:

- Bestehendes Projekt nutzt Vue 2.7 mit Nuxt 2 (Upgrade von 2.6 → 2.7 in Phase 1 erledigt ✅)
- Existierender `styleguide` Ordner als Git-Submodul (Vue 2, Vue CLI 3)
- Design-Token-System mit Theo vorhanden
- Branding erfolgt über SCSS-Dateien mit Variablen-Overrides
- Problem: Viele doppelte Komponenten, inkonsistente Styles, nicht konsequent genutztes Design-System

Vision: Ein stark definiertes und flexibles Design-System, das den Branding-Anforderungen von ocelot.social gerecht wird und eine saubere, schrittweise Migration von Vue 2 nach Vue 3 ermöglicht.

Geplanter Ansatz: Migration vorbereiten - schrittweise neue Komponenten in Vue 3 entwickeln, die das bestehende Design-System respektieren und flexible Branding-Optionen bieten.

# TECHNISCHE GRUNDLAGEN

## 2. Tech-Stack

Komponente | Entscheidung | Notizen
--- | --- | ---
Framework | Vue 3 + Vite | Schnellstes Setup, modernes Tooling
Build-Tool | Vite | Schnelles HMR, einfache Konfiguration
Dokumentation | Storybook 10 | Komponenten-Dokumentation mit Vue 3 + Vite
Styling | Tailwind CSS | Mit CSS Custom Properties für Branding
Testing | Vitest | Vite-nativ, Jest-kompatible API
Paket-Name | @ocelot-social/ui | Unter ocelot-social npm Org
Komponenten-Prefix | Os | OsButton, OsCard, etc.
Vue 2 Kompatibilität | vue-demi | Library funktioniert in Vue 2 und Vue 3
Varianten-System | CVA | class-variance-authority für typsichere Prop-Varianten
Klassen-Merge | cn() | clsx + tailwind-merge für Klassen-Kombination
Linting | eslint-config-it4c | Enthält: TypeScript, Vue, Prettier, weitere Regeln
Release | release-please | Automatische Versionen und Changelogs
Icons | Hybrid-Architektur | System-Icons in Library, Feature-Icons in App (siehe §4)
Browser-Support | Modern only | Chrome, Firefox, Safari, Edge (letzte 2 Versionen)
SSR | Ja | Nuxt-kompatibel
Dark Mode | Ja, von Anfang an | Alle Komponenten mit Light/Dark Varianten
Lizenz | Apache 2.0 | Permissiv mit Patent-Schutz
Repository | Monorepo | Ocelot-Social/packages/ui/

### Konventionen

Aspekt | Entscheidung | Notizen
--- | --- | ---
Vue API | `<script setup>` | Composition API mit script setup (erfordert Vue 2.7+)
Sprache | Englisch | Code, Comments, Dokumentation
Package Manager | npm | Bereits im Projekt verwendet
Test Coverage | 100% | Vollständige Testabdeckung
Dateinamen | PascalCase | OsButton.vue, OsCard.vue
i18n | Nur Props | Keine Default-Texte in Komponenten
Breakpoints | Tailwind Standard | sm:640, md:768, lg:1024, xl:1280, 2xl:1536
Size Props | Tailwind-Skala | sm, md, lg, xl (komponentenspezifisch)
Rounded Props | Tailwind-Skala (vollständig) | none, sm, md, lg, xl, 2xl, 3xl, full
Shadow Props | Tailwind-Skala (vollständig) | none, sm, md, lg, xl, 2xl
Variant Props | Semantisch (vollständig) | primary, secondary, danger, warning, success, info
Dark Mode | Tailwind CSS-Klassen | Via `dark:` Prefix, kein "inverse" Prop
Prop-Vollständigkeit | Alle oder keine | Wenn Komponente einen Prop unterstützt, dann die gesamte Skala
CSS Variables | Keine Defaults in Library | Webapp definiert Default-Branding, spezialisierte Brandings überschreiben
TypeScript | strict: true | Strikte Typisierung

## 3. Build & Distribution

### Dual-Build Strategie

Die Library bietet zwei Nutzungsmöglichkeiten:

    @ocelot-social/ui
    ├── dist/
    │   ├── index.js              # Vue Komponenten
    │   ├── style.css             # Vorkompilierte Styles (für Nicht-Tailwind)
    │   └── tailwind.preset.js    # Tailwind Preset (für Tailwind-Nutzer)

### Nutzung MIT Tailwind

    // tailwind.config.js
    import ocelotPreset from '@ocelot-social/ui/tailwind.preset'
    export default { presets: [ocelotPreset] }

    // main.js
    import { OsButton } from '@ocelot-social/ui'

### Nutzung OHNE Tailwind

    // main.js
    import { OsButton } from '@ocelot-social/ui'
    import '@ocelot-social/ui/style.css'

### Branding (funktioniert für beide)

    /* branding.css */
    :root {
      --color-primary: rgb(110, 139, 135);
      --button-primary-bg: var(--color-secondary);
    }

Beide Varianten nutzen CSS Custom Properties - Branding ist identisch.

### Webapp-Integration (Entwicklung)

Lokale Entwicklung mit Nuxt Alias:

    // webapp/nuxt.config.js
    export default {
      alias: {
        '@ocelot-social/ui': process.env.LOCAL_UI
          ? path.resolve(__dirname, '../packages/ui/src')
          : '@ocelot-social/ui'
      }
    }

Entwickler startet mit:

    LOCAL_UI=true yarn dev

Release-Check: Bei Ocelot-Release wird geprüft, ob `packages/ui` unreleased Änderungen hat:

    # Commits seit letztem ui-Tag?
    git log --oneline $(git describe --tags --match "ui-v*" --abbrev=0)..HEAD -- packages/ui/

→ Wenn Output vorhanden: UI muss zuerst released werden.

### Webapp-Aufgaben (TODO für Ocelot-Webapp)

Die folgenden Aufgaben müssen in der Webapp umgesetzt werden, nicht in der UI-Library:

Aufgabe | Beschreibung | Status
--- | --- | ---
Branding-Validierung | Test/Workflow der `validateCssVariables()` aufruft und sicherstellt, dass das Default-Branding alle von der Library geforderten CSS-Variablen definiert | ⏳ Offen

Beispiel-Implementierung (Webapp):

    // webapp/tests/branding.spec.ts
    import { validateCssVariables, requiredCssVariables } from '@ocelot-social/ui/tailwind.preset'

    describe('Default Branding', () => {
      it('defines all required CSS variables', () => {
        // Load default branding CSS
        // ...

        const styles = getComputedStyle(document.documentElement)
        const missing: string[] = []

        for (const variable of requiredCssVariables) {
          const value = styles.getPropertyValue(variable).trim()
          if (!value) {
            missing.push(variable)
          }
        }

        expect(missing).toEqual([])
      })
    })

## 4. Icon-Architektur

### Entscheidung: Hybrid-Ansatz

Die Library verwendet eine Hybrid-Architektur für Icons:

    ┌─────────────────────────────────────────────────────────────┐
    │  @ocelot-social/ui (Library)                                │
    ├─────────────────────────────────────────────────────────────┤
    │  src/components/OsIcon/icons/svgs/  # System-Icons          │
    │  ├── check.svg           # Bestätigung, Checkboxen          │
    │  ├── close.svg           # Modal, Chip, Dialoge             │
    │  └── plus.svg            # Hinzufügen, Erstellen            │
    │                                                             │
    │  src/ocelot/icons/svgs/             # Ocelot-Icons          │
    │  └── angle-down.svg      # Dropdown-Pfeil                   │
    │                                                             │
    │  (Weitere System-Icons werden bei Bedarf ergänzt)           │
    └─────────────────────────────────────────────────────────────┘
                                ↓
    ┌─────────────────────────────────────────────────────────────┐
    │  Webapp / Konsumierendes Projekt                            │
    ├─────────────────────────────────────────────────────────────┤
    │  assets/icons/           # Feature-Icons (beliebig viele)   │
    │  ├── user.svg            │
    │  ├── bell.svg            │
    │  ├── heart.svg           │
    │  ├── settings.svg        │
    │  └── ...                 │
    └─────────────────────────────────────────────────────────────┘

### Begründung

1. Library muss standalone funktionieren - Ein OsModal braucht einen Close-Button ohne zusätzliche Konfiguration
2. 616 Icons sind zu viel - Der Styleguide hat 616 Icons, die meisten werden nie gebraucht
3. Feature-Icons gehören zur App - Icons wie `user`, `bell`, `heart` sind Business-Logik
4. Branding-Flexibilität - Verschiedene Ocelot-Instanzen können unterschiedliche Icon-Sets verwenden

### System-Icons (in Library enthalten) ✅

Icon | Verwendung | Status
--- | --- | ---
`check` | Bestätigung, Checkboxen | ✅ implementiert
`close` | Modal, Chip, Dialoge | ✅ implementiert
`plus` | Hinzufügen, Erstellen | ✅ implementiert

Geplant (bei Bedarf ergänzen):

Icon | Verwendung in Komponenten
--- | ---
`chevron-down` | OsSelect, OsDropdown, OsAccordion
`chevron-up` | OsSelect, OsAccordion
`bars` | OsPage (mobile menu)
`search` | OsInput (search variant)

### API-Design (implementiert)

    // OsIcon — System-Icon per Name
    <OsIcon name="close" />
    <OsIcon name="check" size="lg" />

    // OsIcon — Custom Vue-Komponente (hat Vorrang vor name)
    <OsIcon :icon="UserIcon" />

    // OsIcon — Semantic (mit aria-label)
    <OsIcon name="close" aria-label="Schließen" />

    // OsButton — Icon über #icon Slot (icon-system-agnostisch)
    <OsButton variant="primary">
      <template #icon><OsIcon name="check" /></template>
      Speichern
    </OsButton>

### SVG-Loading (vite-svg-icon Plugin)

    // SVGs werden via ?icon Query als Vue-Komponenten geladen
    import CheckIcon from './check.svg?icon'

    // Plugin extrahiert viewBox + <path> und transformiert zu:
    // h('svg', { viewBox, ... }, [h('path', { d })])

### Ocelot-Icons (separates Entry-Point)

    // Dynamisches Loading via import.meta.glob
    import { ocelotIcons } from '@ocelot-social/ui/ocelot'

    // Filename → PascalCase: angle-down.svg → IconAngleDown
    // Returns Record<string, () => VNode>

### Aktuelle Icon-Statistik

Quelle | Anzahl | Status
--- | --- | ---
Styleguide (_all) | 616 | Nicht übernehmen (FontAwesome 4 komplett)
Webapp (svgs) | 238 | Feature-Icons, bleiben in Webapp
Library (system) | 3 | ✅ check, close, plus
Ocelot-Icons | 82 | ✅ Feature-Icons + Kategorie-Icons (separates Entry-Point)

## 5. Design-Token & Branding

### 3-Stufen Token-System

    ┌─────────────────────────────────────────────────────────┐
    │  1. BASE TOKENS (Rohwerte)                              │
    │     --color-green: rgb(23, 181, 63);                    │
    │     --color-teal: rgb(110, 139, 135);                   │
    │     --space-small: 16px;                                │
    └────────────────────────┬────────────────────────────────┘
                             ↓
    ┌─────────────────────────────────────────────────────────┐
    │  2. SEMANTIC TOKENS (Bedeutung)                         │
    │     --color-primary: var(--color-green);                │
    │     --color-secondary: var(--color-teal);               │
    │     --text-color-base: var(--color-neutral-20);         │
    └────────────────────────┬────────────────────────────────┘
                             ↓
    ┌─────────────────────────────────────────────────────────┐
    │  3. COMPONENT TOKENS (Komponenten-spezifisch)           │
    │     --button-primary-bg: var(--color-primary);          │
    │     --button-primary-text: var(--color-primary-inverse);│
    │     --card-bg: var(--background-color-base);            │
    └─────────────────────────────────────────────────────────┘

### Branding-Flexibilität

Jedes Branding kann auf jeder Ebene eingreifen:

    /* Standard */
    :root {
      --button-primary-bg: var(--color-primary);
    }

    /* Yunite-Branding: Button nutzt Secondary statt Primary */
    :root {
      --button-primary-bg: var(--color-secondary);
    }

### Kompatibilität mit bestehendem System

- Bestehende SCSS-Variablen (`$color-primary`) → CSS Custom Properties (`--color-primary`)
- Theo bleibt als Token-Quelle, generiert zusätzlich CSS Variables
- Tailwind Theme nutzt CSS Variables: `bg-primary` → `var(--color-primary)`

### CVA + Tailwind + CSS-Variablen

Wie CVA funktioniert:

CVA (Class Variance Authority) mappt Props typsicher auf Tailwind-Klassen:

    // button.variants.ts
    export const buttonVariants = cva(
      'inline-flex items-center font-medium',  // Basis-Klassen
      {
        variants: {
          variant: {
            primary: 'bg-[var(--color-primary)] text-[var(--color-primary-contrast)]',
            danger: 'bg-[var(--color-danger)] text-[var(--color-danger-contrast)]',
          },
          size: {
            sm: 'h-8 px-3 text-sm',
            md: 'h-10 px-4 text-base',
          },
        },
        defaultVariants: { variant: 'primary', size: 'md' },
      }
    )

    // Aufruf:
    buttonVariants({ variant: 'primary', size: 'sm' })
    // → 'inline-flex items-center font-medium bg-[var(--color-primary)] ... h-8 px-3 text-sm'

Was ist via Branding überschreibbar?

Ebene | Überschreibbar | Beispiel
--- | --- | ---
Farben | ✅ Ja (CSS-Variablen) | `--color-primary`, `--color-danger`
Hover-Farben | ✅ Ja (CSS-Variablen) | `--color-primary-hover`
Kontrast | ✅ Ja (CSS-Variablen) | `--color-primary-contrast`
Größen | ❌ Nein (Tailwind-Skala) | `h-10`, `px-4`, `text-base`
Abstände | ❌ Nein (Tailwind-Skala) | `rounded-md`, `gap-2`
Einzelne Instanz | ✅ Ja (class-Prop) | `<OsButton class="h-12" />`

Branding-Beispiel:

    /* branding/default.css */
    :root {
      --color-primary: rgb(23, 181, 63);
      --color-primary-contrast: rgb(255, 255, 255);
      --color-primary-hover: rgb(18, 140, 49);
    }

    /* branding/yunite.css (überschreibt) */
    :root {
      --color-primary: rgb(110, 139, 135);
      --color-primary-contrast: rgb(255, 255, 255);
      --color-primary-hover: rgb(90, 115, 112);
    }

Die Rolle von cn() (clsx + tailwind-merge):

    import { clsx } from 'clsx'
    import { twMerge } from 'tailwind-merge'

    export function cn(...inputs) {
      return twMerge(clsx(inputs))
    }

    // Beispiel: Custom class überschreibt CVA-Werte
    cn('h-10 px-4', 'h-12 px-8')  // → 'h-12 px-8' (letzte gewinnt)

Architektur-Übersicht:

    ┌─────────────────────────────────────────────────────────────┐
    │  CSS-Variablen (Branding-überschreibbar)                    │
    │  --color-primary, --color-danger, --color-*-hover, etc.     │
    └─────────────────────────────────────────────────────────────┘
                                  ↓
    ┌─────────────────────────────────────────────────────────────┐
    │  CVA-Varianten (nutzen CSS-Variablen)                       │
    │  bg-[var(--color-primary)], text-[var(--color-contrast)]    │
    └─────────────────────────────────────────────────────────────┘
                                  ↓
    ┌─────────────────────────────────────────────────────────────┐
    │  Tailwind-Klassen (feste Skala, konsistent)                 │
    │  h-8, h-10, h-12, px-3, px-4, rounded-md, text-sm           │
    └─────────────────────────────────────────────────────────────┘
                                  ↓
    ┌─────────────────────────────────────────────────────────────┐
    │  cn() + class-Prop (Escape-Hatch für Einzelfälle)           │
    │  <OsButton class="h-20 rounded-full" />                     │
    └─────────────────────────────────────────────────────────────┘

Designentscheidung:

- Farben sind flexibel (Branding via CSS-Variablen)
- Größen sind konsistent (Tailwind-Skala, nicht überschreibbar)
- `class`-Prop erlaubt Ausnahmen für Einzelfälle

# PROZESSE & QUALITÄT

## 6. CI/CD & Release

### Release-Workflow (release-please)

Manifest-Modus für Monorepo:

- Nur Commits in `packages/ui/` werden betrachtet
- Eigene Versionierung unabhängig vom Hauptrepo
- Automatischer Changelog nur für dieses Paket

    ┌─────────────────────────────────────────────────────────────────┐
    │                    RELEASE WORKFLOW                             │
    ├─────────────────────────────────────────────────────────────────┤
    │  1. Commit mit Conventional Commits Format                      │
    │  2. release-please erstellt Release-PR                          │
    │  3. PR merge → Version bump + Changelog                         │
    │  4. Automatisch: npm publish + Storybook deploy                  │
    └─────────────────────────────────────────────────────────────────┘

Konfigurationsdateien:

- `.release-please-manifest.json` - Versionen
- `release-please-config.json` - Paket-Konfiguration

### GitHub Workflows

Workflow | Trigger | Beschreibung
--- | --- | ---
Lint | Push/PR | eslint-config-it4c (TypeScript + Vue + Prettier)
Test | Push/PR | Vitest Unit-Tests
Build | Push/PR | Vite Build verifizieren
Release | Push to main | release-please PR erstellen
Publish | Release created | npm publish + Storybook deploy

### Qualitätssicherung bei PRs

    PR erstellt
        ↓
    ┌─────────────┐
    │   Lint      │──→ eslint-config-it4c (TS + Vue + Prettier)
    ├─────────────┤
    │   Tests     │──→ Vitest
    ├─────────────┤
    │   Build     │──→ Vite
    └─────────────┘
        ↓
    Alle grün → Merge erlaubt

### Automatische Deployments

Event | Aktion
--- | ---
Release erstellt | npm publish
Release erstellt | Storybook build + deploy auf Server

Storybook Deploy (Webhook):

1. GitHub sendet Webhook bei Release-Event
2. Server empfängt Webhook
3. Server führt `scripts/deploy-storybook.sh` aus (Teil des Repos)
4. Script: git pull → npm ci → storybook build → copy to webroot

### GitHub Workflows (vollständige Liste)

Workflow | Trigger | Tool | Beschreibung
--- | --- | --- | ---
lint | Push/PR | eslint-config-it4c | Code-Stil prüfen
typecheck | Push/PR | TypeScript | Typ-Fehler finden
test | Push/PR | Vitest | Unit-Tests
test-a11y | Push/PR | axe-core | Accessibility-Tests
test-visual | Push/PR | Playwright | Visual Regression Screenshots
build | Push/PR | Vite | Build verifizieren
build-storybook | Push/PR | Storybook | Dokumentation bauen
size-check | Push/PR | size-limit | Bundle-Größe prüfen
release | Push main | release-please | Release-PR erstellen
publish | Release | npm | Auf npm veröffentlichen
deploy-docs | Release | Webhook | Storybook auf Server deployen
check-ui-release | Ocelot Release | Git | Prüft unreleased UI-Änderungen

### Erweiterte Qualitätssicherung

Maßnahme | Tool | Beschreibung
--- | --- | ---
Visual Regression | Playwright | Screenshot-Vergleiche bei Änderungen
Accessibility | @axe-core/playwright | A11y-Prüfung integriert in Visual Tests
Bundle Size | size-limit | Warnung wenn Library-Größe Schwellwert überschreitet

### Migrations-Absicherung

Feature Parity Checklist (pro Komponente):

    ┌─────────────────────────────────────────────────────────┐
    │  KOMPONENTE: [Name]                                     │
    ├─────────────────────────────────────────────────────────┤
    │  [ ] Alle Props der alten Komponente übernommen         │
    │  [ ] Alle Events identisch                              │
    │  [ ] Alle Slots vorhanden                               │
    │  [ ] Default-Werte identisch                            │
    │  [ ] Visuell identisch (Screenshot-Vergleich)           │
    │  [ ] A11y mindestens gleich gut                         │
    │  [ ] Dokumentation vollständig                          │
    └─────────────────────────────────────────────────────────┘

Deprecation Warnings: Nach erfolgreicher Migration einer Komponente werden in der Vue 2 Webapp Warnungen eingebaut:

    // In alter Komponente
    if (process.env.NODE_ENV === 'development') {
      console.warn('[DEPRECATED] Button: Bitte @ocelot-social/ui verwenden')
    }

## 7. Dokumentation & DX

### Storybook als Komponenten-Dokumentation

Die Komponenten werden über Storybook dokumentiert und auf einer öffentlichen Webseite für Entwickler zugänglich gemacht.

Features für Entwickler:

- Interaktive Playgrounds zum Ausprobieren
- Alle Varianten und Zustände auf einen Blick
- Copy-paste-fertige Code-Snippets
- Suchfunktion
- Props-Dokumentation mit Typen und Defaults

Hosting:

- Eigener Server (öffentlich zugänglich)
- Static Build via `storybook build`
- Deployment bei jedem Release

Workflow:

    Komponente entwickeln → Storybook Story schreiben → Build → Deploy auf Server

## 8. Migrationsstrategie

### Grundprinzipien

    ┌─────────────────────────────────────────────────────────────────┐
    │                    MIGRATIONSSTRATEGIE                          │
    ├─────────────────────────────────────────────────────────────────┤
    │  1. Library vollständig in Vue 3 schreiben                      │
    │  2. vue-demi für Vue 2 Kompatibilität                           │
    │  3. Komponente für Komponente migrieren                         │
    │  4. Jede Komponente: vollständig getestet + in Storybook         │
    │  5. Nach Migration: Duplikate entfernen, Varianten konsolidieren│
    └─────────────────────────────────────────────────────────────────┘

### Ablauf pro Komponente

    ┌──────────────┐    ┌──────────────┐    ┌──────────────┐    ┌──────────────┐    ┌──────────────┐
    │   ANALYSE    │ →  │    SPEC      │ →  │   DEVELOP    │ →  │     QA       │ →  │  INTEGRATE   │
    ├──────────────┤    ├──────────────┤    ├──────────────┤    ├──────────────┤    ├──────────────┤
    │ Bestehende   │    │ Props        │    │ Vue 3 Code   │    │ Alle Tests   │    │ In Vue 2     │
    │ Varianten    │    │ Varianten    │    │ Unit Tests   │    │ grün         │    │ Projekt      │
    │ identifiz.   │    │ Zustände     │    │ Storybook     │    │ Visual Regr. │    │ einbinden    │
    │ Duplikate    │    │ A11y         │    │ Stories      │    │ A11y Check   │    │              │
    │ finden       │    │ Tokens       │    │              │    │ Review       │    │ Alte Komp.   │
    │              │    │              │    │              │    │              │    │ entfernen    │
    └──────────────┘    └──────────────┘    └──────────────┘    └──────────────┘    └──────────────┘

### Komponenten-Protokoll

Pro Komponente wird eine Status-Datei im Komponenten-Ordner geführt (Colocation):

    packages/ui/src/components/OsButton/
    ├── OsButton.vue
    ├── OsButton.spec.ts
    ├── button.variants.ts
    ├── index.ts
    └── STATUS.md          ← Status-Datei hier

Inhalt:

- Übersicht (Implementierungsstatus, Tests, A11y)
- Implementierte Features (Props, Slots, Events)
- Fehlende Features (aus KATALOG.md)
- Test-Coverage
- Architektur (Datei-Struktur, CVA-Pattern)
- Nächste Schritte
- Changelog

Begründung für Colocation:

- Status-Datei direkt beim Code auffindbar
- Einfacher zu aktualisieren bei Änderungen
- Kein separater docs/ Ordner notwendig
- Folgt dem Prinzip "Code und Dokumentation zusammen"

### Qualitätsanforderungen pro Komponente

Anforderung | Beschreibung
--- | ---
Vollständig getestet | Unit-Tests für alle Props, Varianten, Edge-Cases
Storybook Stories | Alle Varianten und Zustände dokumentiert
Detaillierte Spec | Props, Events, Slots, A11y vor Implementierung definiert
Token-basiert | Nutzt ausschließlich Design Tokens, keine hardcoded Werte
Vue 2 kompatibel | Funktioniert via vue-demi im bestehenden Projekt
A11y geprüft | axe-core Tests bestanden
Visual Regression | Screenshot-Baseline erstellt
Feature Parity | Checkliste abgehakt (alle Props, Events, Slots)

### Konsolidierungsziele

Bei der Migration werden:

- Duplikate eliminiert - nur eine kanonische Version pro Komponente
- Unnötige Varianten entfernt - nur tatsächlich genutzte Varianten
- Zusammenführbare Komponenten vereint - z.B. verschiedene Button-Typen → ein Button mit Props
- Inkonsistenzen bereinigt - einheitliche API, Naming, Patterns

## 9. Dokumentationsstrategie

> Siehe ausführliche Version in §15 Dokumentationsstrategie (Details). Kurzzusammenfassung: Generierte Docs (vue-component-meta) + Manuell (Storybook).

## 10. Kompatibilitätstests

> Vue 2/3 × Tailwind/CSS Testmatrix. Details siehe §18.

Kurzfassung:

- 4 Example Apps: vue3-tailwind, vue3-css, vue2-tailwind, vue2-css
- Vitest Unit-Tests mit Vue 2.7 und Vue 3.4
- Playwright E2E für alle 4 Kombinationen
- Package-Validierung: publint, arethetypeswrong

# REFERENZ & HISTORIE

## 11. Entscheidungen

> 73 Entscheidungen in 9 Kategorien

### Vision & Ziele

# | Frage | Entscheidung | Begründung
--- | --- | --- | ---
1 | Hauptziel | Migration vorbereiten | Schrittweise Vue 2 → Vue 3

### Tech-Stack

# | Frage | Entscheidung | Begründung
--- | --- | --- | ---
2 | Framework | Vue 3 + Vite | Modern, schnell, gute DX
3 | Dokumentation | Storybook 10 | Storybook 1.0-beta hängt mit Tailwind v4 + Node 25, Storybook funktioniert sofort
4 | Styling | Tailwind + CSS Variables | Modern + Branding-kompatibel
6 | Testing | Vitest | Vite-nativ, Jest-kompatibel
9 | Vue 2 Kompatibilität | vue-demi | Library funktioniert in beiden Vue-Versionen
16 | Linting | eslint-config-it4c | TypeScript + Vue + Prettier + weitere Regeln
34 | Vue API | `<script setup>` | Composition API mit script setup
36 | Package Manager | npm | Bereits im Projekt verwendet
41 | TypeScript | strict: true | Strikte Typisierung
43 | Vue 2 Minimum | Vue 2.7 | Erforderlich für `<script setup>` Support
69 | Varianten-System | CVA (class-variance-authority) | Typsichere Props, Composable, DX wie shadcn/ui
70 | Klassen-Utility | cn() (clsx + tailwind-merge) | Bedingte Klassen + Tailwind-Deduplizierung
71 | Komponenten-Status | STATUS.md im Komponenten-Ordner | Colocated mit Code, nicht in separatem docs/

### Build & Distribution

# | Frage | Entscheidung | Begründung
--- | --- | --- | ---
8 | Paket-Format | npm Library | Wiederverwendbar
25 | Paket-Name | @ocelot-social/ui | Unter ocelot-social npm Org
31 | Build-Strategie | Dual-Build | Tailwind Preset + Vorkompilierte CSS
32 | Lizenz | Apache 2.0 | Permissiv mit Patent-Schutz
33 | Repository | Monorepo | Ocelot-Social/packages/ui/
42 | Ordnerstruktur | packages/ui | Monorepo-Standard, erweiterbar
44 | Dev-Linking | Nuxt Alias | LOCAL_UI=true für lokale Library

### CI/CD & Release

# | Frage | Entscheidung | Begründung
--- | --- | --- | ---
15 | Release-Tool | release-please (Manifest) | Monorepo-kompatibel, nur packages/ui Änderungen
17 | CI Workflows | Lint, Test, Build | Qualitätssicherung bei jedem PR
18 | npm Publish | Automatisch bei Release | Nach release-please PR merge
19 | Doku-Deploy | Automatisch bei Release | storybook build + deploy
45 | Release-Check | Git-basiert | Prüft unreleased UI-Änderungen vor Ocelot-Release
46 | Storybook Deploy | Webhook + Script | Server zieht und baut bei Release
50 | GitHub Workflows | 12 Workflows | Vollständige CI/CD Pipeline

### Testing & Qualität

# | Frage | Entscheidung | Begründung
--- | --- | --- | ---
20 | Visual Regression | Playwright Screenshots | Visueller Vergleich bei Änderungen
21 | Accessibility | @axe-core/playwright | A11y integriert in Visual Tests
22 | Bundle Size | size-limit | Warnung bei Größenüberschreitung
23 | Feature Parity | Checkliste pro Komponente | Sicherstellen dass alle Features migriert
37 | Test Coverage | 100% | Vollständige Testabdeckung
57 | Kompatibilitätstests | 4er-Matrix + CI | Vue 2/3 × Tailwind/CSS (siehe §18)

### Design-System & Branding

# | Frage | Entscheidung | Begründung
--- | --- | --- | ---
5 | Token-System | 3-Stufen (Base/Semantic/Component) | Maximale Branding-Flexibilität
29 | Dark Mode | Ja, von Anfang an | Alle Komponenten mit Light/Dark
30 | Icons | Hybrid-Architektur | System-Icons in Library, Feature-Icons in App
51 | Icon-Architektur | Hybrid | ~10 System-Icons in Library, Rest in App (siehe §4)
59 | Size Props | Tailwind-Skala (sm, md, lg, xl) | Komponentenspezifisch, OsButton nutzt sm-xl
60 | Rounded Props | Tailwind-Skala (none, sm, md, lg, xl, 2xl, 3xl, full) | Konsistenz mit Tailwind border-radius
61 | Shadow Props | Tailwind-Skala (none, sm, md, lg, xl, 2xl) | Konsistenz mit Tailwind box-shadow
62 | Variant Props | Semantisch (primary, secondary, danger, warning, success, info) | Übliche UI-Farbvarianten
63 | Dark Mode Handling | CSS-Klassen (`dark:` Prefix) | Standard-Tailwind-Pattern, keine "inverse" Props
64 | Prop-Vollständigkeit | Alle Werte einer Skala | Konsistente API, keine Teilmengen pro Komponente
65 | CSS Variable Defaults | Keine Defaults in Library | Webapp definiert Branding, Library ist design-agnostisch
66 | Branding-Hierarchie | Webapp → Spezialisiertes Branding | Default-Branding in Webapp, Overrides pro Instanz
67 | Variable-Validierung | Runtime-Check in Development | `validateCssVariables()` warnt bei fehlenden Variablen
68 | Branding-Test (Webapp) | CI-Test in Webapp | Webapp testet, dass Default-Branding alle Library-Variablen definiert
72 | Webapp ↔ Maintenance Sharing | Webapp als Source of Truth | Kein separates "shared" Package, maintenance importiert aus webapp/ (siehe §16a)
73 | Daten-Entkopplung | ViewModel/Mapper Pattern | Komponenten kennen nur ViewModels, Mapper transformieren API-Daten (siehe §16b)

### Komponenten-API & Konventionen

# | Frage | Entscheidung | Begründung
--- | --- | --- | ---
26 | Komponenten-Prefix | Os | OsButton, OsCard, etc.
27 | Browser-Support | Modern only | Letzte 2 Versionen von Chrome, Firefox, Safari, Edge
28 | SSR | Ja | Nuxt-kompatibel
35 | Sprache | Englisch | Code, Comments, Docs
38 | Dateinamen | PascalCase | OsButton.vue, OsCard.vue
39 | i18n | Nur Props | Keine Default-Texte in Komponenten
40 | Breakpoints | Tailwind Standard | sm, md, lg, xl, 2xl
55 | Komponenten-Abgrenzung | Entscheidungsbaum | Library: präsentational, Webapp: Business-Logik (siehe §16)

### Dokumentation

# | Frage | Entscheidung | Begründung
--- | --- | --- | ---
13 | Doku-Hosting | Eigener Server | Öffentlich zugängliche Komponenten-Doku
14 | Doku-Zugang | Öffentlich | Für alle Entwickler frei zugänglich
47 | Komponenten-Protokoll | STATUS.md im Komponenten-Ordner | Colocated: src/components/OsButton/STATUS.md
52 | Docs-Generierung | vue-component-meta | Komponenten-Tabelle aus Code generiert
53 | Docs CI-Check | GitHub Workflow | Prüft JSDoc-Coverage und README-Aktualität
54 | Nach Migration | ARCHITECTURE.md | PROJEKT.md → ARCHITECTURE.md, KATALOG.md archivieren

### Migration & Prozess

# | Frage | Entscheidung | Begründung
--- | --- | --- | ---
7 | Komponenten-Reihenfolge | Nach Bedarf | Flexibel, Token-System zuerst
10 | Migrations-Ablauf | Komponente für Komponente | Kontrolliert, schrittweise
11 | Vor-Analyse | Vollständige Analyse | Duplikate/Probleme vor Migration identifizieren
12 | Spezifikation | Detailliert vor Implementierung | Props, Varianten, A11y vorher definieren
24 | Deprecation Warnings | Console Warnings | Hinweise in alter Codebase
48 | Katalogisierung | KATALOG.md | Unterbrechbar, Webapp + Styleguide
49 | Fortschritt | Berechenbar | Pro Phase und Gesamt
56 | Externe Abhängigkeiten | ✅ Gelöst | eslint-config-it4c v0.8.0 ist modular
58 | Komplexitätsanalyse | Dokumentiert in §19 | Risikofaktoren, Parallelisierbarkeit, Aufwandstreiber

## 12. Arbeitsprotokoll

Datum | Beschreibung | Ergebnis
--- | --- | ---
2026-02-04 | Projektstart | Ordner und Planungsdokument erstellt
2026-02-04 | Tech-Stack Entscheidungen | Alle Kernentscheidungen getroffen
2026-02-04 | Migrationsstrategie | vue-demi, Komponente-für-Komponente, Analyse-First
2026-02-04 | Dokumentation | Komponenten-Doku auf eigenem Server, öffentlich zugänglich
2026-02-04 | CI/CD & Release | release-please, GitHub Workflows, automatisches npm publish
2026-02-04 | Erweiterte QA | Visual Regression, A11y Tests, Bundle Size Check
2026-02-04 | Migrations-Absicherung | Feature Parity Checklist, Deprecation Warnings
2026-02-04 | Naming & Paket | @ocelot-social/ui, Os-Prefix
2026-02-04 | Plattform | Modern Browsers, SSR-kompatibel, Dark Mode
2026-02-04 | Build & Icons | Dual-Build (Tailwind + CSS), Hybrid-Architektur (~10 System-Icons)
2026-02-04 | Organisatorisch | Apache 2.0, bleibt im Monorepo
2026-02-04 | Konventionen | script setup, Englisch, npm, 100% Coverage, PascalCase
2026-02-04 | Weitere | Nur Props (kein i18n), Tailwind Breakpoints, strict TS
2026-02-04 | Ordnerstruktur | packages/ui statt styleguide-vue3
2026-02-04 | Konfliktanalyse | Vue 2.7 Upgrade als Voraussetzung für script setup
2026-02-04 | Webapp-Integration | Nuxt Alias für lokale Entwicklung, Git-basierter Release-Check
2026-02-04 | Prozesse | QA-Schritt pro Komponente, Komponenten-Protokoll, KATALOG.md
2026-02-04 | Fortschritt | Berechenbar für Gesamt und Einzelschritte
2026-02-04 | Phase 1 abgeschlossen | Vue 2.7 Upgrade erfolgreich, alle Tests bestanden
2026-02-04 | Icon-Architektur | Hybrid-Ansatz: ~10 System-Icons in Library, Feature-Icons in App
2026-02-04 | Dokumentationsstrategie | Hybrid: Generiert (vue-component-meta) + Manuell, CI-geprüft
2026-02-04 | Abgrenzung Library/Webapp | Entscheidungsbaum + Checkliste für Komponenten-Zuordnung
2026-02-04 | Externe Abhängigkeit | eslint-config-it4c blockiert Linting-Setup, Workaround dokumentiert
2026-02-04 | Kompatibilitätstests | 4er-Matrix (Vue 2/3 × Tailwind/CSS), Example Apps, Playwright E2E
2026-02-07 | Storybook statt Histoire | Histoire 1.0-beta hängt mit Tailwind v4 + Node 25 (kein Output, 100% CPU). Storybook 10 funktioniert sofort.
2026-02-07 | Storybook Greyscale-Theme | Komponenten in Graustufen - verdeutlicht, dass Farben von der App kommen, nicht von der Library.
2026-02-07 | CSS Token-System | requiredCssVariables mit 18 Farb-Variablen (6 Farben × 3 Werte) befüllt.
2026-02-07 | Storybook Workflow | ui-storybook.yml für Build + Artifact Upload.
2026-02-07 | Docker Setup | Dockerfile (dev + prod), ui-docker.yml Workflow, docker-compose Services.
2026-02-07 | Storybook Build Fix | viteFinal entfernt vite-plugin-dts und build-css für Storybook-Build. Stories aus Coverage ausgeschlossen.
2026-02-07 | Docs-Check Script | scripts/check-docs.ts prüft: Story-Existenz, JSDoc für Props, Varianten-Abdeckung. ui-docs.yml Workflow.
2026-02-04 | Phasen umbenannt | 0.5→1, 1→2, 2→3, 3→4, 4→5 (nur ganzzahlige Phasen)
2026-02-04 | Dokument-Konsolidierung | §13 Zahlen korrigiert, §14 Link entfernt, §16 Reihenfolge, Terminologie vereinheitlicht
2026-02-04 | Komplexitätsanalyse | §20 hinzugefügt: Risikofaktoren, Parallelisierbarkeit, Aufwandstreiber pro Komponente
2026-02-04 | Phase 2 gestartet | Vite + Vue 3 Projekt initialisiert, vue-demi, Vitest, Package-Struktur
2026-02-04 | Build-System | vite.config.ts mit Library-Mode, vite-plugin-dts für Types, vite-tsconfig-paths
2026-02-04 | Testing | Vitest in vite.config.ts integriert, Plugin-Tests geschrieben
2026-02-04 | Dokumentation | README.md mit Installation und Usage (Tree-Shaking vs Plugin)
2026-02-04 | Tailwind-Konventionen | Size, Rounded, Shadow, Variant - vollständige Skalen, Dark Mode via CSS
2026-02-04 | Tailwind v4 Setup | @tailwindcss/vite Plugin, Dual-Build (style.css + tailwind.preset)
2026-02-04 | Prop-Types | src/types.d.ts mit Size, Rounded, Shadow, Variant
2026-02-04 | Branding-Architektur | Keine Defaults in Library, Webapp definiert Branding, validateCssVariables()
2026-02-07 | ESLint Setup | eslint-config-it4c v0.8.0 eingerichtet (Vue 3, Vitest, Prettier)
2026-02-07 | GitHub Workflows | ui-lint.yml, ui-test.yml (100% Coverage), ui-build.yml (Build + Verify)
2026-02-07 | .tool-versions | Node 25.5.0 zentral definiert, Workflows nutzen node-version-file
2026-02-07 | Vue 2/3 Matrix | vue2 + @vitejs/plugin-vue2, CI Matrix-Tests, .npmrc legacy-peer-deps
2026-02-07 | Vue 2 Test-Strategie geändert | Inline-Matrix entfernt (Peer-Dependency-Konflikte), Vue 2 wird via Example Apps getestet
2026-02-07 | Example Apps erstellt | vue2-app und vue3-app mit Vitest, ui-compatibility.yml Workflow
2026-02-07 | ESLint Examples | Eigene eslint.config.ts + prettier.config.mjs pro Example App, Lint im Compatibility-Workflow
2026-02-07 | Type Assertions | `as unknown as Plugin` für CI-Kompatibilität bei verlinkten Packages
2026-02-07 | Package-Validierung | publint + arethetypeswrong, separate .d.cts für CJS, afterBuild Hook in vite-plugin-dts
2026-02-07 | 4er Example Apps Matrix | vue3-tailwind, vue3-css, vue2-tailwind, vue2-css; Workflow mit Matrix-Strategie
2026-02-07 | Release & Publish | release-please Manifest-Config, ui-release.yml Workflow mit npm publish
2026-02-07 | CONTRIBUTING.md | Entwickler-Leitfaden mit Setup, Komponenten-Erstellung, Code-Standards, Testing, Commits
2026-02-07 | Dependabot | Konfiguration für UI-Package und alle 4 Example Apps hinzugefügt
2026-02-07 | CSS-Build | Tailwind CLI im closeBundle Hook für style.css Generierung
2026-02-07 | CVA Setup | class-variance-authority, clsx, tailwind-merge als Dependencies
2026-02-07 | cn() Utility | Tailwind-Klassen-Merge Funktion (clsx + tailwind-merge) mit Tests
2026-02-07 | OsButton | Erste Komponente mit CVA-Varianten (variant, size, fullWidth) implementiert
2026-02-07 | ESLint Fixes | vue/max-attributes-per-line off (Prettier), import-x/no-relative-parent-imports off
2026-02-07 | STATUS.md Konvention | Komponenten-Status als STATUS.md im Komponenten-Ordner (Colocation statt docs/)
2026-02-07 | OsButton STATUS.md | Status-Datei erstellt mit Feature-Übersicht, fehlenden Features, Test-Coverage, Architektur
2026-02-07 | CVA-Dokumentation | §5 erweitert: CVA + Tailwind + CSS-Variablen Architektur, Branding-Überschreibbarkeit
2026-02-07 | Accessibility Tests | vitest-axe mit axe-core, Custom TypeScript-Declarations, OsButton a11y-Tests
2026-02-07 | Completeness Check | check-docs.ts → check-completeness.ts, docs:check → verify, ui-docs.yml → ui-verify.yml
2026-02-07 | Visual Regression Tests | Playwright mit colocated Tests (*.visual.spec.ts), ui-visual.yml Workflow
2026-02-07 | Colocated Screenshots | screenshots/ im Komponenten-Ordner statt separatem e2e/
2026-02-07 | Visual Test Coverage | verify prüft ob alle Stories Visual Tests haben (--kebab-case URL)
2026-02-07 | Phase 2 abgeschlossen | Alle 26 Aufgaben erledigt, bereit für Phase 3
2026-02-08 | Keyboard Tests | describe('keyboard accessibility') mit 4 Tests, verify prüft Existenz
2026-02-08 | ESLint Plugins | eslint-plugin-storybook installiert, flat/recommended Config
2026-02-08 | A11y Konsolidierung | vitest-axe → @axe-core/playwright, checkA11y() in Visual Tests
2026-02-08 | Greyscale Theme WCAG | Farben für 4.5:1 Kontrast angepasst (warning, info, secondary)
2026-02-08 | Test-Vereinfachung | *.a11y.spec.ts entfernt, A11y via checkA11y() in Visual Tests
2026-02-08 | Completeness Update | verify prüft: Story, Visual, checkA11y(), Keyboard, Varianten
2026-02-08 | Phase 2 Konsolidierung | Dependencies aufgeräumt (vitest-axe, axe-core entfernt)
2026-02-08 | Projekt-Optimierung | src/test/setup.ts entfernt, @storybook/vue3 entfernt, README.md fix
2026-02-08 | Package Updates | size-limit 12.0.0, eslint-plugin-jsdoc 62.5.4, vite-tsconfig-paths 6.1.0
2026-02-08 | TODO: eslint-config-it4c | Muss auf ESLint 10 aktualisiert werden (aktuell inkompatibel)
2026-02-08 | Phase 3: vue-demi Integration | vue-demi zur Webapp hinzugefügt, Webpack-Alias für Vue 2.7 Kompatibilität
2026-02-08 | Phase 3: Webpack-Alias | @ocelot-social/ui$ und style.css$ Aliase für yarn-linked Package
2026-02-08 | Phase 3: isVue2 Render | OsButton mit isVue2 Check: Vue 2 attrs-Objekt, Vue 3 flat props
2026-02-08 | Phase 3: CSS-Spezifität | UI-Library CSS nach Styleguide laden (styleguide.js Plugin)
2026-02-08 | Phase 3: Jest vue-demi | Custom Mock (`__mocks__/@ocelot-social/ui.js`) mit Module._load Patch, defineComponent von Vue.default, vueDemiSetup.js, 979 Tests ✅
2026-02-08 | Phase 3: Button-Styles | Variants angepasst: font-semibold, rounded, box-shadow, h-[37.5px]
2026-02-08 | Phase 3: Erste Integration | UserTeaserPopover.vue verwendet `<os-button>`
2026-02-08 | Phase 3: Visueller Test | Manueller Vergleich OsButton vs ds-button erfolgreich ✅
2026-02-08 | Phase 3: v8 ignore | Vue 2 Branch in OsButton mit `/* v8 ignore */` für 100% Coverage in Vitest
2026-02-08 | Phase 3: Docker Build | ui-library Stage in Dockerfile + Dockerfile.maintenance, COPY --from=ui-library
2026-02-08 | Phase 3: CI-Fix | Relativer Pfad `file:../packages/ui` statt absolut für yarn install außerhalb Docker
2026-02-08 | Phase 3: Storybook Fix | TypeScript-Fehler in Stories behoben (`default` aus args entfernt)
2026-02-08 | Phase 3: attrs/listeners | OsButton forwarded jetzt attrs + $listeners für Vue 2 (getCurrentInstance)
2026-02-08 | Phase 3: Jest Mock erweitert | Alle Composition API Funktionen (computed, ref, watch, etc.) im Mock
2026-02-08 | Phase 3: 15 Buttons migriert | GroupForm, EmbedComponent, DonationInfo, CommentCard, MapStylesButtons, GroupMember, embeds, notifications, privacy, terms-and-conditions-confirm
2026-02-08 | Phase 3: Test-Updates | privacy.spec.js Selektoren, notifications Snapshot, DonationInfo.spec.js
2026-02-08 | OsButton: appearance Prop | Neue `appearance` Prop: `filled` (default), `outline`, `ghost` - ermöglicht base-button Stile
2026-02-08 | OsButton: xs Size | Exakte Pixel-Werte für base-button --small: h-26px, px-8px, text-12px, rounded-5px
2026-02-08 | OsButton: outline primary | Grüner Rahmen + grüner Text + hellgrüner Hintergrund-Tint (rgba(25,122,49,0.18))
2026-02-08 | OsButton: ghost primary | Transparenter Hintergrund, grüner Text, Hover füllt grün, Active dunkler
2026-02-08 | OsButton: Focus Style | `focus:outline-dashed focus:outline-1` statt ring (wie base-button)
2026-02-08 | OsButton: Active State | active:bg-[var(--color-VARIANT-hover)] für dunkleren Hintergrund beim Drücken
2026-02-08 | Visuelle Validierung | Tracking-Tabelle in PROJEKT.md für manuelle Button-Vergleiche (4/16 validiert)
2026-02-08 | Storybook Grayscale Theme | Vollständige CSS-Variablen: default, active-states, contrast-inverse
2026-02-08 | Tailwind Source Filter | `@import "tailwindcss" source(none)` - verhindert Markdown-Scanning
2026-02-08 | Button Variants Konsistenz | Alle 21 compound variants mit korrekten active-states (`--color-*-active`)
2026-02-08 | CSS-Variablen erweitert | `--color-secondary/warning/success/info-active` in ocelot-ui-variables.scss
2026-02-08 | Story Dokumentation | "Medium (37.5px)" → "Medium (36px)" korrigiert
2026-02-08 | Playwright Toleranz | `maxDiffPixelRatio: 0.03` für Cross-Platform Font-Rendering
2026-02-09 | Disabled-Styles korrigiert | CSS-Variablen `--color-disabled`, filled: grauer Hintergrund statt opacity
2026-02-09 | terms-and-conditions-confirm | Read T&C Button → `appearance="outline" variant="primary"`
2026-02-09 | Visuelle Validierung | 10/16 Buttons validiert (terms-and-conditions-confirm.vue abgeschlossen)
2026-02-09 | Disabled:active/hover Fix | CSS-Regeln in index.css mit höherer Spezifität für sofortige disabled-Darstellung
2026-02-09 | notifications.vue | Check All + Uncheck All → `appearance="outline" variant="primary"`
2026-02-09 | Visuelle Validierung | 14/16 Buttons validiert (notifications.vue abgeschlossen)
2026-02-09 | embeds.vue | Allow All → `appearance="outline" variant="primary"`
2026-02-09 | Disabled Border-Fix | CSS-Regeln in index.css: `border-style: solid` + `border-width: 0.8px` bei :disabled
2026-02-09 | Visuelle Validierung abgeschlossen | 16/16 Buttons validiert (100%) ✅ Milestone 5 erfolgreich
2026-02-09 | Button-Analyse erweitert | 14 weitere Buttons identifiziert (ohne icon/circle/loading) → Scope: 16/35
2026-02-09 | Scope auf ~90 erweitert | ~60 weitere Buttons mit icon/circle/loading identifiziert
2026-02-09 | Milestone 4a: 8 Buttons | DisableModal, DeleteUserModal, ReleaseModal, ContributionForm, EnterNonce, MySomethingList, ImageUploader (2x)
2026-02-09 | ImageUploader CSS-Fix | `position: absolute !important` für crop-confirm (überschreibt OsButton `relative`)
2026-02-09 | §16a hinzugefügt | Webapp ↔ Maintenance Code-Sharing: Webapp als Source of Truth (Entscheidung #69)
2026-02-09 | §16b hinzugefügt | Daten-Entkopplung: ViewModel/Mapper Pattern für API-agnostische Komponenten (Entscheidung #70)
2026-02-09 | NotificationMenu.vue | 2 Buttons migriert (ghost primary), padding-top Fix für vertical-align Unterschied
2026-02-09 | Milestone 4a abgeschlossen | 6 weitere Buttons migriert: donations.vue (Save), profile/_id/_slug.vue (Unblock, Unmute), badges.vue (Remove), notifications/index.vue (Mark All Read), ReportRow.vue (More Details)
2026-02-10 | Wasserfarben-Farbschema | Greyscale-Theme → Aquarell-Farben (Ultramarin, Dioxazin-Violett, Alizarin, Ocker, Viridian, Cöruleum), WCAG AA konform
2026-02-10 | Stories konsolidiert | Primary/Secondary/Danger/Default entfernt → AllVariants; AllSizes/AllAppearances/Disabled/FullWidth zeigen alle 7 Varianten
2026-02-10 | Appearance: Filled/Outline/Ghost | Einzelne Stories umbenannt und mit allen 7 Varianten erweitert
2026-02-10 | Playground-Story | Interaktive Controls (argTypes nur in Playground, nicht global)
2026-02-10 | Einheitlicher Border | `border-[0.8px] border-solid border-transparent` als Base-Klasse für alle Appearances
2026-02-10 | WCAG 2.4.7 Fix | Default-Variante: `focus:outline-none` → `focus:outline-dashed focus:outline-current`
2026-02-10 | Keyboard A11y Test | Playwright-Test fokussiert alle Buttons und prüft `outlineStyle !== 'none'`
2026-02-10 | data-appearance Attribut | OsButton rendert `data-appearance` auf `<button>`; CSS-Selektoren nutzen `[data-appearance="filled"]` statt escaped Tailwind-Klassen
2026-02-10 | Code-Review Fixes | Unit-Tests: spezifischere Assertions (Compound-Variant-Logik), Trailing Spaces in Testnamen, ESLint restrict-template-expressions Fix
2026-02-10 | CSS-Linting | `@eslint/css` + `tailwind-csstree` für Tailwind v4 Custom Syntax; `excludeCSS()` Helper verhindert JS-Regel-Konflikte; Regeln: no-empty-blocks, no-duplicate-imports, no-invalid-at-rules
2026-02-10 | CI-Workflow-Trigger | 9 UI-Workflows von `on: push` auf `push`+`pull_request` mit Branch-Filter (`master`) und Path-Filter (`packages/ui/**` + Workflow-Datei) umgestellt
2026-02-10 | custom-class entfernt | `custom-class` Prop (entfernt aus OsButton) → `class` Attribut in notifications.vue, MapStylesButtons.vue, EmbedComponent.vue (4 Stellen); Snapshot aktualisiert
2026-02-10 | Vue 3 Template-Fix | `this.$t()` → `$t()` in CommentCard.vue (this im Template in Vue 3 nicht verfügbar)
2026-02-11 | Icon-Slot implementiert | Benannter `#icon` Slot für OsButton, slot-basiert statt Icon-Prop (icon-system-agnostisch)
2026-02-11 | Icon-Wrapper Klassen | Tailwind-Utility-Klassen direkt auf `<span>`: `inline-flex items-center shrink-0 h-[1.2em] [&>svg]:h-full [&>svg]:w-auto [&>svg]:fill-current`
2026-02-11 | VNode Text-Erkennung | `hasText` prüft VNode-Children auf sichtbaren Inhalt; whitespace-only → icon-only Verhalten
2026-02-11 | Gap & Margin Logik | `gap-2` bei Icon+Text, `-ml-1` bei Icon, `-ml-1 -mr-1` bei Icon-Only (optischer Ausgleich)
2026-02-11 | 4 neue Stories | Icon, IconOnly, IconSizes, IconAppearances mit Inline-SVG Komponenten (CheckIcon, CloseIcon, PlusIcon)
2026-02-11 | Playground erweitert | Reaktiver Icon-Selektor (none/check/close/plus) + Label-Text-Control via `computed()`
2026-02-11 | Storybook: components Option | Funktionale Komponenten müssen in `components` registriert werden, nicht in `setup()` return
2026-02-11 | Storybook: CSS nicht in index.css | Storybook lädt eigene `storybook.css`, nicht `src/styles/index.css` → Utility-Klassen direkt verwenden
2026-02-11 | SVG-Targeting | `[&>svg]` statt `[&>*]` für Icon-Sizing (BaseIcon rendert `<span><svg>`, Wrapper-Span darf nicht beeinflusst werden)
2026-02-11 | my-email-address migriert | Save-Button: `<os-button variant="primary">` mit `<template #icon><base-icon name="check" /></template>`
2026-02-11 | Code-Optimierung | `ICON_CLASS` Konstante extrahiert, `iconMargin` Variable, vereinfachte `hasText`-Logik (kein Symbol.for)
2026-02-11 | Größenabhängiger Gap | `gap-1` (4px) für xs/sm, `gap-2` (8px) für md/lg/xl bei Icon+Text
2026-02-11 | Größenabhängiger Margin | Kein negativer Icon-Margin bei xs/sm (voller Padding-Abstand zur Button-Grenze)
2026-02-11 | DisableModal.vue | Confirm-Button migriert: `danger filled icon="exclamation-circle"` → `variant="danger"` + `#icon` Slot
2026-02-11 | DeleteUserModal.vue | Confirm-Button migriert: identisches Pattern wie DisableModal
2026-02-11 | CtaUnblockAuthor.vue | Button migriert: `filled icon="arrow-right"` → `variant="primary"` + `#icon` Slot, OsButton importiert
2026-02-11 | LocationSelect.vue | Icon-only Close-Button migriert: `ghost size="small" icon="close"` → `variant="primary" appearance="ghost" size="sm"` + aria-label
2026-02-11 | CategoriesSelect.vue | v-for Buttons migriert: dynamisches `:icon` → `#icon` Slot, `:filled` → `:appearance`, CSS `.base-button` → `button`
2026-02-11 | profile/_id/_slug.vue | Chat-Button migriert: `icon="chat-bubble"` → `variant="primary" appearance="outline" full-width` + `#icon` Slot
2026-02-11 | verify.vue korrigiert | Kein Button vorhanden (Eintrag aus Milestone-Liste entfernt)
2026-02-11 | PaginationButtons.vue | 2 circle icon-only Buttons migriert: `outline primary circle` + `#icon` Slot + aria-label
2026-02-11 | OsButton: circle Prop | `circle` Prop: `rounded-full p-0` + größenabhängige Breiten (CIRCLE_WIDTHS Map)
2026-02-11 | OsButton: loading Prop | Animierter SVG-Spinner mit `aria-busy="true"`, Button auto-disabled bei loading
2026-02-11 | Spinner-Architektur | Beide Animationen (rotate + dash) auf `<circle>` Element; SVG ist statischer Container; Chrome-Compositing-Bug-Workaround
2026-02-11 | Spinner-Zentrierung | Icon-Buttons: Spinner über Icon (translate-basiert, overflow:visible); Text-Buttons: Spinner im Button-Container (inset-0 m-auto)
2026-02-11 | animations.css | Keyframes `os-spinner-dash` + `os-spinner-rotate` in separate CSS-Datei ausgelagert
2026-02-11 | min-width pro Größe | `min-w-[26px]`/`min-w-[36px]`/`min-w-12`/`min-w-14` in button.variants.ts (verhindert zu kleine leere Buttons)
2026-02-11 | Code-Optimierung | OsButton ~250→207 Zeilen: buttonData geteilt, SPINNER_PX vereinfacht, redundante cn() entfernt, getCurrentInstance nur Vue 2
2026-02-11 | 5 neue Unit-Tests | default type, data-appearance, min-w, icon-only loading, circle gap-1; gesamt: 76 Tests
2026-02-11 | Milestone 4b abgeschlossen | icon ✅, circle ✅, loading ✅ — alle OsButton-Props implementiert

2026-02-11

Milestone 4c: 59 Buttons

Chat (2), AddChatRoomByUserSearch (1), CommentCard (1), CommentForm (2), ComponentSlider (2), ContributionForm (1), DeleteData (1), EmbedComponent (1), FilterMenu (1), HeaderButton (2), CategoriesFilter (2), OrderByFilter (2), EventsByFilter (2), FollowingFilter (3), GroupButton (1), ConfirmModal (2), ReportModal (2), Password/Change (1), PasswordReset/Request (1), PasswordReset/ChangePassword (1), Registration/Signup (1), ReleaseModal (1), ImageUploader (2), CreateInvitation (1), Invitation (2), ProfileList (1), ReportRow (1), MySomethingList (3), ActionButton (1), pages/index (2), profile/add-post (1), post/blur-toggle (1), groups/slug (3), settings/index (1), admin/users (2), blocked-users (1), data-download (1), muted-users (1), groups/index (1), enter-nonce (1)

2026-02-11 | type="submit" Pattern | OsButton hat `type="button"` als Default; alle Form-Submit-Buttons brauchen explizit `type="submit"`
2026-02-11 | !!errors Pattern | `formErrors` ist ein Objekt, nicht Boolean; OsButton `disabled` Prop erwartet Boolean → `!!formErrors` nötig
2026-02-11 | CSS-Selector Pattern | `.base-button` → `> button` oder `button`; Position/Dimensions brauchen `!important` für Tailwind-Override
2026-02-11 | Disabled border-color | Outline disabled border von `var(--color-disabled)` auf `var(--color-disabled-border,#e5e3e8)` mit Fallback
2026-02-11 | Phase 3 abgeschlossen | 132 `<os-button>` Tags in 78 Dateien, 0 `<base-button>` in Templates verbleibend
2026-02-11 | Password/Change.vue Fix | `!!errors` für disabled-Prop (formErrors ist Objekt)
2026-02-11 | CommentForm.vue Fix | `type="submit"` fehlte + `!!errors` für disabled-Prop
2026-02-11 | GroupForm.vue ds-button | Letzter `<ds-button>` in Webapp → `<os-button>` mit `#icon` Slot migriert
2026-02-11 | OsButton.spec.ts TS-Fix | `size` aus `Object.entries` als Union Type gecastet (`as 'sm'
2026-02-11 | Coverage 100% | `v8 ignore start/stop` für Vue 2 Branch, `v8 ignore next` für defensive `
2026-02-11 | Scope: 133 Buttons | 133 `<os-button>` Tags in 79 Dateien, 0 `<base-button>` + 0 `<ds-button>` verbleibend
2026-02-12 | data-variant Attribut | OsButton rendert `data-variant` auf `<button>` (konsistent mit `data-appearance`), ermöglicht CSS-Selektoren wie `button[data-variant="danger"]`
2026-02-12 | notifications.spec.js | Test-API korrigiert: `wrapper.find()` (Vue Test Utils) → `screen.getByText()` (Testing Library), `button.disabled` statt `button.attributes('disabled')`
2026-02-12 | FilterMenu Regressionsbug | `appearance="ghost"` war hardcoded statt dynamisch; `filterActive` Computed Property existierte aber war nicht genutzt → `:appearance="filterActive ? 'filled' : 'ghost'"`
2026-02-12 | FilterMenu.spec.js | Test von CSS-Klasse `--filled` auf `data-appearance="filled"` Attribut-Selektor umgestellt
2026-02-12 | CtaUnblockAuthor.vue | Typo `require: true` → `required: true` (Vue ignorierte die Prop-Validierung)
2026-02-12 | LocationSelect.vue Fixes | `event.target.value` → `this.currentValue` (Button hat kein value), `@click.native` → `@click` (Vue 3), `aria-label` via i18n
2026-02-12 | i18n: actions.clear | Neuer Key in allen 9 Sprachdateien: en=Clear, de=Zurücksetzen, fr=Effacer, es=Borrar, it=Cancella, nl=Wissen, pl=Wyczyść, pt=Limpar, ru=Очистить
2026-02-12 | OsButton JSDoc | Slot-Dokumentation (`@slot default`, `@slot icon`) für vue-component-meta/Storybook autodocs
2026-02-12 | OsButton xs entfernt | `isSmall` von `['xs', 'sm'].includes(size)` auf `size === 'sm'` vereinfacht (xs ist kein gültiger Size-Wert)
2026-02-12 | Strikte Typisierung | `type Size = NonNullable<ButtonVariants['size']>`, `Record<Size, ...>` für CIRCLE_WIDTHS + SPINNER_PX; `props.size!` → `(props.size ?? 'md') as Size`
2026-02-12 | animations.css | Stylelint-konforme Formatierung: eine Deklaration pro Zeile, Leerzeilen zwischen Keyframe-Stufen
2026-02-12 | OsButton Refactoring | `vueAttrs()` Helper für Vue 2/3 Attribut-Handling, Einmal-Variablen durch `cn()` ersetzt, `children` inline; 77 Tests, 100% Coverage
2026-02-12 | CSS @import Reihenfolge | `@import "./animations.css"` vor `@source`-Direktiven verschoben (CSS-Spec: @import vor anderen At-Rules)
2026-02-12 | CustomButton Cleanup | `isEmpty` aus `data()` entfernt — reine Utility-Funktion braucht keine Vue-Reaktivität
2026-02-12 | notifications.spec.js | Doppelten `beforeEach` konsolidiert; `wrapper` von Modulebene in `describe`-Block verschoben
2026-02-12 | Style-Scoping | MenuLegend.vue: `<style scoped>` hinzugefügt; ReportModal + DeleteUserModal: CSS-Selektoren mit Komponenten-Prefix
2026-02-12 | data-test Selektoren | LocationSelect (`clear-location-button`) + HashtagsFilter (`clear-search-button`): spezifischere Test-Selektoren
2026-02-12 | Vue 3 Compat Fixes | FollowButton: `.native` entfernt; FilterMenu: `slot`/`slot-scope` → `<template #default>`; HashtagsFilter: `this.$t()` → `$t()`
2026-02-12 | A11y: aria-label | GroupContentMenu icon-only Button: `$t('group.contentMenu.menuButton')`; PaginationButtons: `$t('pagination.previous/next')`
2026-02-12 | i18n Keys | `pagination.previous/next` + `group.contentMenu.menuButton` in allen 9 Sprachdateien angelegt
2026-02-12 | Modal Konsistenz | DisableModal + DeleteUserModal: `appearance="filled"` + `:loading="loading"` auf Danger-Buttons
2026-02-12 | Loading State | my-email-address/index.vue: `loadingData` hinzugefügt + `finally` Block für Reset
2026-02-12 | MapButton #icon Slot | Icon von Default-Slot in `<template #icon>` verschoben (konsistent mit allen anderen Buttons)
2026-02-12 | Dead Code entfernt | MySomethingList.vue: `.icon-button` CSS-Klasse (nach Migration nicht mehr verwendet)
2026-02-12 | Button-Wrapper-Analyse | 15 OsButton-Wrapper klassifiziert: 4 Smart (Apollo/Vuex), 4 Presentational, 7 Borderline; GroupButton + MapButton als Inline-Kandidaten identifiziert
2026-02-12 | compat/ Konzept | Separates Verzeichnis für temporäre Migrations-Wrapper (nicht von check-completeness.ts erfasst); BaseIcon als erster Kandidat (131 Nutzungen)
2026-02-13 | data-test Selektoren | ~10 Komponenten: `data-test` Attribute für robuste Test-Selektoren (unmute-btn, unblock-btn, follow-btn, join-leave-btn, login-btn, load-all-connections-btn, content-menu-button)
2026-02-13 | Cypress Selektoren | 4 Step-Definitions: `.user-content-menu button` / `.content-menu button` → `[data-test="content-menu-button"]`
2026-02-13 | Spec-Selektoren | FollowList, FollowButton, LoginButton, CtaJoinLeaveGroup, ReportRow, muted-users, blocked-users: generische `button` → `[data-test="..."]`
2026-02-13 | A11y: aria-label | ~15 icon-only Buttons: aria-label hinzugefügt (admin/users, AddChatRoom, EmbedComponent, groups, profile, CustomButton, HeaderMenu, ImageUploader, ContentMenu, HeaderButton, InviteButton, LoginButton, blocked/muted-users)
2026-02-13 | Zustandsabhängiges aria-label | post/_id/_slug: `$t(blurred ? 'post.sensitiveContent.show' : 'post.sensitiveContent.hide')`
2026-02-13 | ComponentSlider aria-label | Interpoliertes Label: `$t('component-slider.step', { current: index + 1, total: ... })`
2026-02-13 | i18n Keys (6 neue) | `actions.search`, `actions.close`, `actions.menu`, `site.navigation`, `post.sensitiveContent.show/hide`, `component-slider.step` in allen 9 Sprachdateien
2026-02-13 | Loading-State Fixes | DisableModal + ReleaseModal: `finally { this.loading = false }` für Reset
2026-02-13 | Bugfixes | ChangePassword: `!!errors`; Password/Change: `disabled` aus data() entfernt + 2 tote Tests; MenuBar: unbenutztes `ref` entfernt
2026-02-13 | Button-Props | GroupForm cancel: `variant="default" appearance="filled"`; donations/LoginForm/EnterNonce: `appearance="filled"` ergänzt
2026-02-13 | CSS-Selektoren | LoginForm: `.login-form button` → `.login-form button[type='submit']`; pages/index: redundante Klasse auf BaseIcon entfernt
2026-02-13 | JoinLeaveButton | `.native` von `@mouseenter`/`@mouseleave` entfernt (Vue 3 Kompatibilität)
2026-02-13 | MySomethingList | `:title` + `:aria-label` auf Edit/Delete-Buttons (Tooltip beibehalten neben Accessibility)
2026-02-13 | OsButton Klasse | `os-button` CSS-Klasse auf Button-Element für Branding-Kompatibilität (#9211)
2026-02-14 | ESLint Config Update | eslint-config-it4c v0.11.2: Flat Config, path alias `#src`, CSS-Linting, security/detect-non-literal-fs-filename, n/no-sync, n/shebang (#9233)
2026-02-14 | check-completeness | Parallelisiertes File-Reading, breitere Regex für Keyboard-Tests, Playground-Tests ignoriert
2026-02-14 | Release v0.0.2 | @ocelot-social/ui v0.0.2 veröffentlicht
2026-02-14 | `as` Prop | Polymorphe OsButton-Komponente: `as` Prop für dynamischen Tag/Komponente (`button`, `a`, `nuxt-link`, `router-link`); moderner Standard (Headless UI, Radix Vue)
2026-02-14 | Naming: tag → as | `tag` → `as` umbenannt nach Recherche moderner UI-Libraries (Headless UI, Radix Vue, Chakra UI, PrimeVue nutzen `as`)
2026-02-14 | Disabled nur für button | `disabled`/`type`/`loading` nur bei `as="button"` (Links haben kein natives `disabled`); `aria-disabled`/`tabindex` Logik entfernt
2026-02-14 | Polymorphic Story | Neue Story `Polymorphic` mit Varianten, Icons, disabled-Vergleich; Playground mit `as`-Selektor (button/a)
2026-02-14 | nuxt-link Migration | 15 `<nuxt-link>`/`<a>`-Wrapper → `as="nuxt-link"`/`as="a"` in 15 Webapp-Dateien; invalides HTML (`<button>` in `<a>`) eliminiert
2026-02-14 | CustomButton konsolidiert | v-if/v-else für `<a>`/`<nuxt-link>` Wrapper → einzelner `<os-button :as="linkTag" v-bind="linkProps">` mit Computed Properties
2026-02-14 | NotificationMenu konsolidiert | 2 separate Buttons (kein Badge / mit Badge) zu einem zusammengeführt — counter-icon zeigt bei `count=0` kein Badge
2026-02-14 | CSS-Selektor Fix | pages/index.vue: `button.post-add-button-top/bottom` → `.post-add-button-top/bottom` (nuxt-link rendert `<a>`, nicht `<button>`)
2026-02-14 | Profil-Spacing | profile/_id/_slug.vue: `v-if` auf ds-grid-item (kein leerer Abstand), symmetrisches Padding `$space-x-small`
2026-02-15 | OsIcon Komponente | Neue Komponente: `name` (System-Icon), `icon` (Custom Component), `size` (xs-2xl); Vue 2/3 via vue-demi h()
2026-02-15 | vite-svg-icon Plugin | Custom Vite Plugin: extrahiert viewBox + `<path>` aus SVG, transformiert zu Vue h()-Aufruf via `?icon` Query
2026-02-15 | System-Icons | 3 SVG-Icons in Library: check, close, plus (viewBox 0 0 32 32, stroke-basiert); SYSTEM_ICONS Registry + SystemIconName Type
2026-02-15 | Icon-Größen | CVA-Varianten: xs(0.75em), sm(0.875em), md(1.2em default), lg(1.5em), xl(2em), 2xl(2.5em) — em-basiert für Kontext-Skalierung
2026-02-15 | Icon A11y | Decorative: `aria-hidden="true"` (default); Semantic: `role="img"` + `aria-label` wenn Label-Prop vorhanden
2026-02-15 | OsIcon in OsButton | OsButton nutzt OsIcon statt inline SVG-Elemente für Icon-Slot-Rendering
2026-02-15 | Ocelot-Icons | Separates Entry-Point (`ocelot.mjs`): dynamisches Icon-Loading via `import.meta.glob('**/*.svg', { query: '?icon' })`
2026-02-15 | webapp → ocelot | `src/webapp/` → `src/ocelot/` umbenannt; Stories, Tests, Exports angepasst; konsistentes Naming
2026-02-15 | OsIcon Tests | 211 Zeilen Unit-Tests (Rendering, Sizes, A11y, CSS, System-Icons); Visual Tests mit checkA11y(); Keyboard A11y; 100% Coverage
2026-02-15 | OsButton Stories | Bereinigt: Inline-SVG-Komponenten durch OsIcon ersetzt; WithAriaLabel-Story entfernt; InheritColor-Story vereinfacht
2026-02-15 | check-completeness | Erweitert für ocelot/ Verzeichnis; unterstützt OsIcon-Patterns
2026-02-15 | svg-icon.d.ts | TypeScript-Deklaration für `?icon` Import-Query (Component-Typ)
2026-02-15 | BaseIcon → OsIcon Migration | 131 `<base-icon>` in 70+ Dateien → `<os-icon :icon="icons.xxx">`, 0 BaseIcon verbleibend
2026-02-15 | 82 Ocelot-Icons | Von 1 auf 82 Icons: Feature-Icons + 17 Kategorie-Icons aus Webapp kopiert
2026-02-15 | vite-svg-icon erweitert | Unterstützt jetzt `<rect>`, `<circle>`, `<polygon>`, `<polyline>`, `<ellipse>`, `<line>` (war path-only)
2026-02-15 | SVG-Minifizierung | Alle 21 neuen SVGs auf Single-Line minifiziert (Multiline brach JS-String-Literale im Plugin)
2026-02-15 | Kategorie-Icons | DB-String → `toCamelCase()` → `ocelotIcons[key]` Lookup in Category, CategoriesFilter, CategoriesSelect, admin/categories
2026-02-15 | MenuLegend.vue Fix | `legendItems` von `data()` → `computed` (`data()` läuft vor `created()`, `this.icons` undefined)
2026-02-15 | HeaderMenu.vue Fix | Map-Button Icon-Größe: `size="xl"` + negative Margin (OsIcon md=1.2em vs BaseIcon --large=2.2em)
2026-02-15 | ShowPassword.vue Fix | `:data-test="iconName"` entfernt (Icon ist jetzt Render-Function statt String)
2026-02-15 | Test-Updates (8 Specs) | Category, ProfileAvatar, CounterIcon, ReportRow, ActionButton, ComponentSlider, ShowPassword, LoginForm: BaseIcon → OsIcon + ocelotIcons
2026-02-15 | 8 Snapshots gelöscht | Stale Snapshot-Dateien entfernt nach BaseIcon → OsIcon Migration
2026-02-15 | CSS Migration | `.base-icon` → `.os-icon` in main.scss und Category/index.vue
2026-02-15 | Jest Mock ocelot | `test/__mocks__/@ocelot-social/ui/ocelot.js` für ocelotIcons in Jest-Umgebung
2026-02-18 | OsSpinner Komponente | Neue Komponente: size (xs-2xl, em-basiert), currentColor, role="status", aria-label; Vue 2/3 via h() + isVue2
2026-02-18 | OsSpinner Decorative | `aria-hidden="true"` unterdrückt role/aria-label; OsButton nutzt OsSpinner als Komponente (decorative)
2026-02-18 | ButtonSize Type | `ButtonSize` (sm/md/lg/xl) exportiert aus button.variants.ts; types.d.ts Kommentar: Size ist Vokabular, nicht Pflicht
2026-02-18 | OsSpinner Webapp-Migration | 4 Stellen migriert: ImageUploader, profile, groups, admin; LoadingSpinner gelöscht
2026-02-18 | ds-space centered Bugfix | `<ds-space centered>` → `<div style="text-align:center;padding:48px 0">` in 3 Seiten (Styleguide-Bug)
2026-02-18 | Admin Spinner Fix | `<ApolloQuery>` → `apollo`-Option + `$apollo.loading`; SSR-Prefetch verhinderte Loading-State im Client
2026-02-18 | filterStatistics Fix | `delete data.__typename` → Destructuring `{ __typename, ...rest }` (keine Mutation des Originalobjekts)
2026-02-18 | infinite-loading Spinner-Slot | OsSpinner im `spinner`-Slot von vue-infinite-loading in 3 Seiten (index, profile, groups); einheitliches Spinner-Design
2026-02-19 | BaseCard → OsCard Migration | ~30 Webapp-Dateien: `<base-card>` → `<os-card>` mit lokalen Imports; CSS-Fixes für Tailwind p-6 Override, outline highlight, child→descendant selectors
2026-02-19 | #imageColumn/#topMenu inline | LoginForm, RegistrationSlider, password-reset: BaseCard-Slots → inline Layout mit `.os-card.--columns` CSS in main.scss
2026-02-19 | Tests & Stories migriert | 16 Spec-Dateien, 4 Story-Dateien, 12+2 Cypress E2E Step-Definitions: base-card → os-card Selektoren
2026-02-19 | BaseCard gelöscht | BaseCard.vue Komponente + base-components.js Plugin entfernt; nuxt.config, maintenance config, testSetup bereinigt
2026-02-19 | CSS-Fixes | ContributionForm Media-Query Selektoren, ProfileList Spezifität, InternalPage $space-small, OsCard highlight outline-1 Tests
2026-02-19 | Code-Quality | SocialMedia Props typisiert, LoginForm querySelector, redundante client-only entfernt, NotificationsTable optional chaining, HashtagsFilter doppeltes Mounting
2026-02-19 | Review Fixes (Session 26) | Cypress Kind-Kombinator `.os-card > .title`, OsCard.spec.ts Bitmask-Assertion fix, Maintenance-App Abhängigkeiten als pre-existing bewertet
2026-02-19 | Tier A Migration (Session 27) | 10 triviale ds-* Vue-Wrapper → Plain HTML + CSS-Klassen; `_ds-compat.scss` Utility-Klassen; ~90 Dateien geändert
2026-02-19 | ds-section/placeholder/tag/list | 19 Nutzungen in 13 Dateien → HTML-Elemente mit bestehenden CSS-Klassen aus system.css
2026-02-19 | ds-container | 14 Nutzungen in 12 Dateien → `<div class="ds-container ds-container-{width}">`
2026-02-19 | ds-heading | 31 Nutzungen in 25 Dateien → `<h1-h4 class="ds-heading ds-heading-h{n}">` mit soft/no-margin/align
2026-02-19 | ds-text | 80 Nutzungen in 42 Dateien → `<p/div class="ds-text ds-text-{color} ds-text-size-{size}">`
2026-02-19 | ds-space | 139 Nutzungen in 55+ Dateien → `<div class="ds-mb-{size}">` / `<div class="ds-my-{size}">`; neue Utility-Klassen in _ds-compat.scss
2026-02-19 | ds-flex/ds-flex-item | 103 Nutzungen in 29 Dateien → Plain HTML + CSS `@media` Queries; JS window.innerWidth → CSS Media Queries; `gap` statt negative-margin/padding
2026-02-19 | HTML-Validierung Bugfix | `<p>` mit Block-Level-Kindern → `<div>` in DateTimeRange.vue und verify.vue
2026-02-19 | Test-Fix | Empty.spec.js: `attributes().margin` → `classes().toContain('ds-my-xxx-small')`
2026-02-19 | Review Fixes (Session 28) | ~20 CodeRabbit Review-Kommentare für Tier A PR bearbeitet; Bugfixes, A11y, i18n, Scoping
2026-02-19 | Heading-Semantik | ComponentSlider h1→h3, SearchHeading h1→h5 (ds-heading size-Prop wurde falsch zu h1 migriert)
2026-02-19 | A11y Fixes | Label-for Mismatch (RegistrationSlideEmail), in Aside entfernt, link.id→link.url Key
2026-02-19 | i18n | logout.vue "Logging out..." → $t() mit 9 Sprachen; maintenance alt-Text → $t()
2026-02-19 | CSS Fixes | .buttons Klasse ergänzt (GroupForm), display:flex auf .ds-space-centered, 33%→33.333%, inline-style→Utilities
2026-02-19 | Scoping | Signup.vue, maintenance/index.vue, post/create/_type.vue, post/edit/_id.vue: unscoped → scoped Style-Blöcke
2026-02-19 | Migration-Artefakte | Redundante Wrapper-Divs, tote Attribute (margin="large"), Span→P Block-Level Regression
2026-02-20 | OsBadge Komponente (Session 29) | Neue Komponente: CVA-Varianten (variant, size, shape), h() Render-Function, 16 Tests, 6 Stories
2026-02-20 | ds-chip → OsBadge | 20 Nutzungen in 5 Dateien: GroupTeaser, GroupMember, GroupForm, ContributionForm, groups/_slug
2026-02-20 | ds-tag → OsBadge | 3 Nutzungen in 3 Dateien: Category (shape="square"), Hashtag (shape="square"), PostTeaser (CSS)
2026-02-20 | OsBadge Features | shape-Prop (pill/square), w-fit (Flex-Container-Fix), inline-flex items-center (Icon-Zentrierung)
2026-02-20 | CSS-Variable | --color-default + --color-default-contrast für neutralen Badge-Hintergrund
2026-02-20 | Code-Review Fixes (Session 30) | --color-default-contrast in requiredCssVariables, doppelte --color-default Deklaration entfernt
2026-02-20 | Type Safety | BadgeVariant Typ exportiert, PlaygroundArgs typisiert, redundante Ternäre entfernt
2026-02-20 | Layout-Konsistenz | GroupForm float:right → Flexbox align-self:flex-end, Inline-Style → Tailwind in Stories
2026-02-20 | ARIA Live Regions | role="status"/aria-live="polite" auf 11 Form-Badges (WCAG 4.1.3), live Prop → Standard-Attribute
2026-02-20 | ds-grid → CSS Grid | 10 Dateien migriert: ds-grid/ds-grid-item → native CSS Grid + Klassen
2026-02-20 | ds-table → Plain HTML (Session 31) | 7 Dateien: `<ds-table>` → native `<table>` + CSS-Klassen (.ds-table, .ds-table-col, etc.)
2026-02-20 | Table-CSS | _ds-compat.scss erweitert: .ds-table-wrap, .ds-table, .ds-table-col, .ds-table-head-col, bordered, condensed, alignment
2026-02-20 | fields() entfernt | Computed Properties `fields()`/`tableFields()` aus 7 Dateien entfernt — Labels direkt in `<th>`
2026-02-20 | Scope-Objekte entfernt | `scope.row` Zugriffe → direkte Iteration-Variable (user, tag, member, report)
2026-02-20 | OsNumber Komponente (Session 32) | Neue Komponente: h() Render-Function, requestAnimationFrame Animation (1500ms ease-out), count (required), label, animated Props
2026-02-20 | ds-number + CountTo → OsNumber | 5 Dateien: UserTeaserPopover, TabNavigation, admin/index, profile/_slug, groups/_slug
2026-02-20 | Animation-Stabilität | `tabular-nums` + `min-width: Nch` für stabile Breite während Count-up Animation
2026-02-20 | CountTo.vue gelöscht | vue-count-to Dependency entfernt, followedByCountStartValue/membersCountStartValue Pattern entfernt
2026-02-20 | CSS-Variable --color-text-soft | Neuer Contract-Eintrag in tailwind.preset.ts + ocelot-ui-variables.scss (Label-Farbe)
2026-02-20 | Admin-Label uppercase | `.admin-stats__item .os-number-label { text-transform: uppercase }` per CSS statt neuem Prop
2026-03-13 | OsModal Komponente | Neue Tier 2 Komponente: h() Render-Function, Vue 2/3 via vue-demi, Focus-Trap, Body Scroll-Lock, ESC-Key, Backdrop-Click, A11y (role=dialog, aria-modal, aria-labelledby/aria-label), 37 Unit-Tests, 5 Visual Tests, 100% Coverage
2026-03-13 | OsModal Features | open Prop (v-model:open), title, cancelLabel/confirmLabel, ariaLabel Fallback, footer Scoped-Slot ({confirm, cancel}), Scroll-Fade (top gradient), tabindex=0 auf scrollbarem Content
2026-03-13 | OsModal Events | update:open, confirm, cancel, close (mit Typ: 'confirm'/'cancel'/'close'/'backdrop'), opened
2026-03-13 | OsModal Vue 2 Compat | attrs Forwarding in beiden Vue 2 Branches (closed + open), eventProps() Helper, $listeners Forwarding, $createElement für Icons
2026-03-13 | Modal Webapp-Integration | ConfirmModal + ReportModal nutzen OsModal; Vuex Modal Store entfernt; Modals inline gerendert
2026-03-13 | Modal Bugfixes | z-index Stacking Context Fix (PostTeaser/GroupTeaser), Callback-Promise Propagation (ReportList, MySomethingList), Group Leave Authorization Fix ($nextTick), Cypress .ds-modal → .os-modal
2026-03-13 | Modal A11y | scrollable-region-focusable Fix (tabindex=0), aria-label Fallback wenn kein Title, body overflow save/restore
2026-03-14 | ds-form entkoppelt | Neues `formValidation` Mixin (async-validator): provide/subscribe Pattern, formData/formSchema/formErrors, handleInput/handleInputValid Callbacks; vuelidate komplett entfernt
2026-03-14 | 18 Formulare migriert | CommentForm, ContributionForm, EnterNonce, GroupForm, Password/Change, PasswordReset (2), Registration (5), Signup, MySomethingList, donations, admin/users, settings (3)
2026-03-20 | formValidation Fix | `handleInput()` vor `$validateForm()` aufrufen (Reihenfolge-Bug: handleInput überschrieb handleInputValid bei synchronem async-validator Callback)
2026-03-23 | ds-input → OcelotInput | Neue Webapp-Komponente `OcelotInput.vue`: vereint DsInput + FormItem + InputLabel + InputError in einer Datei. 23 Vue-Dateien migriert mit lokalen Imports (tree-shakeable). formValidation Mixin voll kompatibel. dot-prop Abhängigkeit durch inline `getNestedValue()` ersetzt. 28 Test-Suites, 210 Tests ✅, 7 Snapshots aktualisiert.
2026-03-23 | OcelotInput: ds-icon → os-icon | DsIcon durch OsIcon + resolveIcon() ersetzt. at.svg, envelope.svg, paperclip.svg zu Ocelot-Icons hinzugefügt. Ocelot-Icons Visual Snapshot aktualisiert.
2026-03-23 | ds-select → OcelotSelect | Neue Webapp-Komponente `OcelotSelect.vue`: vereint DsSelect + inputMixin + multiinputMixin (~420 Zeilen). Form-Validation entfernt (von keinem Consumer genutzt). DsChip→OsBadge, DsSpinner→OsSpinner, DsIcon→OsIcon. vue-click-outside durch inline document.addEventListener ersetzt. 3 Dateien migriert, 16 Tests ✅.

2026-03-23

ds-menu → OsMenu/OsMenuItem

Neue packages/ui Komponenten: h() Render, vue-demi, provide/inject, dropdown Prop für Popup-Variante. CSS in src/styles/index.css (integriert in style.css Build). 17 Nutzungen in 11 Dateien migriert. Action-Menüs nutzen link-tag default 'a' statt router-link. router-link Stub global in testSetup.js. Vite closeBundle Hook: ui.css in style.css gemergt. 273 UI-Tests, 108 Webapp-Tests ✅. 0 ds- Komponenten-Tags verbleibend in Webapp.*

2026-03-28

Maintenance-App entkoppelt (Session 35)

Eigenständiges Nuxt 4-Projekt unter `maintenance/`. Vue 3, SSR off, `nuxt generate` → statisches HTML. Abhängigkeiten: @ocelot-social/ui (OsButton, OsIcon, OsCard), @nuxtjs/i18n v10 (11 Sprachen), floating-vue (VDropdown für LocaleSwitch), Tailwind CSS v4. Eigene LocaleSwitch-Komponente (OsButton ghost/circle + OsIcon language + VDropdown). Eigene Locale-Dateien, Branding-CSS, Docker + nginx. Tests: vitest + @nuxt/test-utils. Kein Vuex/Apollo/v-tooltip — vollständig von Webapp entkoppelt. Validiert packages/ui als echten Shared Layer.

## 13. Komponenten-Katalog

> Detaillierte Katalogisierung in separater Datei: KATALOG.md

### Zusammenfassung (aus KATALOG.md)

Quelle | Gesamt | Status
--- | --- | ---
Webapp | 139 | ✅ Katalogisiert
Styleguide | 38 | ✅ Katalogisiert — 23 in Webapp genutzt

Styleguide-Migration:

Status | Komponenten
--- | ---
✅ UI-Library | OsButton, OsIcon, OsSpinner, OsCard, OsBadge, OsNumber, OsModal (7)
✅ → Plain HTML | Section, Placeholder, List, ListItem, Container, Heading, Text, Space, Flex, FlexItem, Grid, GridItem, Table, Radio (14) — Tier A/B
✅ → UI-Library | Chip, Tag → OsBadge (2), Number → OsNumber (1) — Tier B
✅ ds-form entkoppelt | Form-Validierung → formValidation Mixin (async-validator), vuelidate entfernt
✅ ds-input → OcelotInput | Webapp-Komponente (23 Dateien), lokale Imports, FormItem/InputLabel/InputError vereint
✅ ds-select → OcelotSelect | Webapp-Komponente (3 Dateien), lokale Imports, click-outside inline, DsChip→OsBadge
✅ → OsMenu/OsMenuItem | Menu, MenuItem (17 Nutzungen → packages/ui, dropdown Prop, eigene CSS)
⬜ Nicht genutzt | Code, CopyField, FormItem, InputError, InputLabel, Page, PageTitle, Logo, Avatar, TableCol, TableHeadCol (11)

## 14. Ressourcen & Links

Projekt:

- Ocelot-Social Repository
- Styleguide (Live)
- Bestehender Styleguide Code: `../../styleguide/`
- Bestehende Webapp: `../../webapp/`
- Design Tokens: `../../styleguide/src/system/tokens/`

Tracking:

- Katalogisierung: `./KATALOG.md`
- Komponenten-Status: `./src/components/*/STATUS.md` (colocated)

Dokumentation:

- Vue 3
- Vite
- Tailwind CSS
- Storybook
- Vitest
- vue-demi

## 15. Dokumentationsstrategie (Details)

### Übersicht

    ┌─────────────────────────────────────────────────────────────┐
    │  GENERIERT (aus Code) - Single Source of Truth              │
    ├─────────────────────────────────────────────────────────────┤
    │  • README.md Komponenten-Tabelle                            │
    │  • Props/Events/Slots Dokumentation                         │
    │  • TypeScript-Typen                                         │
    └─────────────────────────────────────────────────────────────┘
                                +
    ┌─────────────────────────────────────────────────────────────┐
    │  MANUELL                                                    │
    ├─────────────────────────────────────────────────────────────┤
    │  • README.md Installation, Quick Start, Theming             │
    │  • Storybook Stories (interaktive Beispiele)                 │
    │  • ARCHITECTURE.md (Entscheidungen)                         │
    │  • Best Practices, Patterns                                 │
    └─────────────────────────────────────────────────────────────┘

### README.md Struktur

    # @ocelot-social/ui

    ## Übersicht
    - Was ist die Library?
    - Link zu Storybook (Live-Dokumentation)

    ## Installation
    - npm install
    - Peer Dependencies (vue, vue-demi)

    ## Quick Start
    - Minimal-Beispiel (Import + Nutzung)
    - Mit Tailwind / Ohne Tailwind

    ## Theming / Branding
    - CSS Custom Properties überschreiben
    - Beispiel-Branding

    ## Icon-System
    - System-Icons (enthalten)
    - Eigene Icons registrieren

    ## Vue 2 / Vue 3
    - vue-demi Erklärung
    - Kompatibilitätshinweise

    ## Komponenten                    ← GENERIERT
    - Tabelle aller Komponenten
    - Link zu Storybook für Details

    ## Contributing
    - Link zu CONTRIBUTING.md

    ## License
    - Apache 2.0

### Generierung mit vue-component-meta

Tool: `vue-component-meta` (offizielles Vue-Tool)

    // scripts/generate-docs.ts
    import { createComponentMetaChecker } from 'vue-component-meta'

    // Extrahiert aus .vue Dateien:
    {
      name: "OsButton",
      description: "Primary button component for user actions",
      props: [
        {
          name: "variant",
          type: "'primary' | 'secondary' | 'danger'",
          default: "'primary'",
          required: false,
          description: "Visual style variant"
        }
      ],
      events: [{ name: "click", type: "(event: MouseEvent) => void" }],
      slots: [{ name: "default", description: "Button content" }]
    }

Generierte Komponenten-Tabelle:

    | Komponente | Beschreibung | Props | Events | Slots |
    |------------|--------------|-------|--------|-------|
    | OsButton | Primary button for actions | 8 | 1 | 2 |
    | OsCard | Container with header/footer | 5 | 0 | 3 |
    | ... | ... | ... | ... | ... |

### CI-Workflow: docs-check

    # .github/workflows/docs-check.yml
    name: Documentation Check

    on: [push, pull_request]

    jobs:
      docs:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4

          - name: Generate docs from components
            run: npm run docs:generate

          - name: Check for undocumented props
            run: npm run docs:check-coverage
            # Fails if props/events/slots missing JSDoc

          - name: Verify README is up-to-date
            run: npm run docs:verify
            # Compares generated table with README
            # Fails if differences found

Scripts in package.json:

    {
      "scripts": {
        "docs:generate": "tsx scripts/generate-docs.ts",
        "docs:check-coverage": "tsx scripts/check-doc-coverage.ts",
        "docs:verify": "tsx scripts/verify-readme.ts",
        "docs:update": "npm run docs:generate && npm run docs:inject-readme"
      }
    }

### ESLint-Regeln für Dokumentation

    // eslint.config.js
    {
      rules: {
        // Erzwingt JSDoc für exportierte Funktionen/Komponenten
        "jsdoc/require-jsdoc": ["error", {
          require: {
            FunctionDeclaration: true,
            ClassDeclaration: true
          }
        }],
        "jsdoc/require-description": "error",
        "jsdoc/require-param-description": "error"
      }
    }

### Dokumentations-Phasen

Phase | Dokumentation | Status
--- | --- | ---
Phase 2 | README.md Grundgerüst (Installation, Setup) | Manuell
Phase 2 | CONTRIBUTING.md | Manuell
Phase 2 | docs:generate Script einrichten | Automatisiert
Phase 2 | CI docs-check Workflow | Automatisiert
Phase 4 | Storybook Stories pro Komponente | Manuell
Phase 4 | JSDoc in Komponenten | Im Code
Phase 4 | README Komponenten-Tabelle | Generiert
Phase 4 | README Finalisierung | Manuell
Phase 5 | ARCHITECTURE.md (aus PROJEKT.md) | Manuell

### Nach der Migration: Datei-Transformation

    Während Migration:
    ├── PROJEKT.md          # Planungs- und Statusdokument
    ├── KATALOG.md          # Komponenten-Tracking

    Nach Phase 5 (Migration abgeschlossen):
    ├── README.md           # Nutzer-Dokumentation (teilweise generiert)
    ├── CONTRIBUTING.md     # Beitragende
    ├── CHANGELOG.md        # Automatisch via release-please
    ├── ARCHITECTURE.md     # Entscheidungen aus PROJEKT.md §2, §4, §5, §11, §15, §16
    ├── src/components/*/STATUS.md  # Komponenten-Status (colocated, werden beibehalten)
    └── docs/
        └── archive/        # Historische Referenz (optional)
            ├── PROJEKT.md
            └── KATALOG.md

Was wird übernommen nach ARCHITECTURE.md:

- §2 Tech-Stack (gekürzt)
- §5 Design-Token & Branding (Token-Architektur)
- §11 Entscheidungen (relevante, nicht Prozess-bezogene)
- §4 Icon-Architektur
- §9/§15 Dokumentationsstrategie (gekürzt)
- §16 Library vs. Webapp

Was wird archiviert/gelöscht:

- Fortschritt → erledigt
- Aktueller Stand → erledigt
- Meilensteine → erledigt
- §12 Arbeitsprotokoll → historisch interessant, archivieren
- KATALOG.md → nur für Migration relevant, archivieren

# ABGRENZUNGEN

## 16. Library vs. Webapp

### Grundprinzip

    ┌─────────────────────────────────────────────────────────────┐
    │  @ocelot-social/ui (Library)                                │
    ├─────────────────────────────────────────────────────────────┤
    │  • Rein präsentational                                      │
    │  • Keine Business-Logik                                     │
    │  • Keine API-Calls                                          │
    │  • Kein App-State                                           │
    │  • Wiederverwendbar in jedem Vue-Projekt                    │
    └─────────────────────────────────────────────────────────────┘
                                ▲
                                │ nutzt
    ┌─────────────────────────────────────────────────────────────┐
    │  Webapp (Ocelot)                                            │
    ├─────────────────────────────────────────────────────────────┤
    │  • Business-Logik (GraphQL, Auth, etc.)                     │
    │  • App-State (Vuex/Pinia)                                   │
    │  • i18n Texte                                               │
    │  • Routing-Logik                                            │
    │  • Ocelot-spezifische Features                              │
    └─────────────────────────────────────────────────────────────┘

### Entscheidungs-Checkliste

Kriterium | Library ✅ | Webapp ✅
--- | --- | ---
Business-Logik | Keine | Hat GraphQL/API-Calls
App-State | Kein Vuex/Pinia | Braucht Store
i18n | Nur via Props | Nutzt `$t()` direkt
Routing | Nur via Props (`to`) | Nutzt `$router` direkt
Wiederverwendbar | In jedem Vue-Projekt | Nur in Ocelot
Abhängigkeiten | Nur Vue + vue-demi | Ocelot-spezifisch
Styling | Design-Tokens | App-spezifische Styles
Daten | Erhält via Props | Fetcht selbst

### Entscheidungsbaum

    Komponente X
        │
        ▼
    ┌─────────────────────────────────────┐
    │ Hat sie Business-Logik?             │
    │ (API-Calls, Mutations, Auth-Check)  │
    └─────────────────────────────────────┘
        │
        ├── JA ──► WEBAPP
        │
        ▼ NEIN
    ┌─────────────────────────────────────┐
    │ Braucht sie App-State?              │
    │ (Vuex, Pinia, globaler State)       │
    └─────────────────────────────────────┘
        │
        ├── JA ──► WEBAPP
        │
        ▼ NEIN
    ┌─────────────────────────────────────┐
    │ Nutzt sie $t() oder $router direkt? │
    └─────────────────────────────────────┘
        │
        ├── JA ──► WEBAPP (oder refactoren)
        │
        ▼ NEIN
    ┌─────────────────────────────────────┐
    │ Ist sie generisch wiederverwendbar? │
    │ (Könnte in anderem Projekt helfen)  │
    └─────────────────────────────────────┘
        │
        ├── NEIN ──► WEBAPP
        │
        ▼ JA

        ══► LIBRARY

### Quantitative Regel

> Wenn ≥2 Kriterien auf "Webapp" zeigen → WebappWenn alle Kriterien auf "Library" zeigen → Library

### Konkrete Beispiele

Komponente | Entscheidung | Begründung
--- | --- | ---
`OsButton` | Library | Rein präsentational, keine Logik
`OsModal` | Library | UI-Container, Logik via Events
`OsInput` | Library | Generisches Form-Element
`OsAvatar` | Library | Nur Bild + Fallback-Initialen
`OsCard` | Library | Layout-Container
`OsDropdown` | Library | Popover-Mechanik
`FollowButton` | Webapp | GraphQL Mutation, User-State
`PostTeaser` | Webapp | Ocelot-Datenstruktur, Links
`CommentForm` | Webapp | API-Call, Auth-Check
`ConfirmModal` | Webapp | Nutzt OsModal + Callbacks
`LoginForm` | Webapp | Auth-Logik, Routing, i18n
`UserAvatar` | Webapp | Nutzt OsAvatar + User-Daten
`NotificationMenu` | Webapp | GraphQL, Store, i18n

### Composition Pattern für Grenzfälle

Wenn eine Komponente UI- und Business-Anteile hat: Aufteilen

    ┌─────────────────────────────────────────────────────────────┐
    │  LIBRARY: OsModal                                           │
    │  - Overlay, Animation, Backdrop                             │
    │  - close/confirm Events                                     │
    │  - Slots für Content                                        │
    │  - Props: title, confirmLabel, cancelLabel                  │
    └─────────────────────────────────────────────────────────────┘
                            ▲
                            │ nutzt
    ┌─────────────────────────────────────────────────────────────┐
    │  WEBAPP: DeleteUserModal                                    │
    │  - Wraps OsModal                                            │
    │  - GraphQL Mutation                                         │
    │  - i18n Texte via $t()                                      │
    │  - Redirect nach Löschung                                   │
    └─────────────────────────────────────────────────────────────┘

Beispiel-Code:

    <!-- Webapp: DeleteUserModal.vue -->
    <template>
      <OsModal
        :title="$t('user.delete.title')"
        :confirm-label="$t('common.delete')"
        confirm-variant="danger"
        @confirm="handleDelete"
        @cancel="$emit('close')"
      >
        <p>{{ $t('user.delete.warning', { name: user.name }) }}</p>
      </OsModal>
    </template>

    <script setup>
    import { OsModal } from '@ocelot-social/ui'
    import { useDeleteUserMutation } from '~/graphql/mutations'
    import { useRouter } from 'vue-router'

    const props = defineProps(['user'])
    const emit = defineEmits(['close'])
    const router = useRouter()
    const { mutate: deleteUser } = useDeleteUserMutation()

    const handleDelete = async () => {
      await deleteUser({ id: props.user.id })
      emit('close')
      router.push('/')
    }
    </script>

### Checkliste bei neuer Komponente

Vor dem Erstellen einer Komponente diese Fragen beantworten:

    [ ] Wo gehört die Komponente hin? (Entscheidungsbaum durchlaufen)
    [ ] Falls Library: Sind alle Texte via Props?
    [ ] Falls Library: Keine direkten Store/Router Imports?
    [ ] Falls Webapp: Welche Library-Komponenten werden genutzt?
    [ ] Falls Grenzfall: Kann sie aufgeteilt werden?

## 16a. Webapp ↔ Maintenance Code-Sharing

### Problemstellung (historisch)

Die Webapp und Maintenance-App waren verschachtelt (`webapp/maintenance/`). Die Frage war, wie geteilte Business-Komponenten organisiert werden sollten.

### Umgesetzte Lösung: Vollständige Entkopplung ✅ (Session 35, 2026-03-28)

Die Maintenance-App ist eine rein statische Wartungsseite ohne Business-Logik. Daher wurde statt Option C (Webapp als Source) die einfachste Lösung gewählt: Vollständige Entkopplung ohne Webapp-Imports.

    ┌─────────────────────────────────────────────────────────────┐
    │  @ocelot-social/ui                                          │
    │  ─────────────────                                          │
    │  • OsButton, OsIcon, OsCard, OsModal, OsBadge, OsNumber,   │
    │    OsSpinner, OsMenu, OsMenuItem                            │
    │  • Rein präsentational, keine Abhängigkeiten                │
    ├─────────────────────────────────────────────────────────────┤
    │  webapp/                                                    │
    │  ───────                                                    │
    │  • Nuxt 2 (Vue 2.7), importiert @ocelot-social/ui          │
    │  • Alle Business-Komponenten + GraphQL + Vuex              │
    │  • OcelotInput, OcelotSelect (lokale Webapp-Komponenten)   │
    ├─────────────────────────────────────────────────────────────┤
    │  maintenance/                    ← NEU: eigenständig        │
    │  ────────────                                               │
    │  • Nuxt 4 (Vue 3), importiert @ocelot-social/ui            │
    │  • Keine Imports aus webapp/ — vollständig unabhängig       │
    │  • Eigene LocaleSwitch (OsButton + OsIcon + floating-vue)  │
    │  • @nuxtjs/i18n v10, eigene Locale-Dateien                 │
    │  • Tailwind CSS v4, Docker + nginx                          │
    └─────────────────────────────────────────────────────────────┘

### Kriterien für Entwickler

Frage | Antwort
--- | ---
Wo suche ich eine UI-Komponente? | `@ocelot-social/ui` (packages/ui)
Wo suche ich eine Business-Komponente? | `webapp/components/`
Wo erstelle ich maintenance-spezifische Komponenten? | `maintenance/app/components/`
Teilen Webapp und Maintenance Code? | Nein — nur @ocelot-social/ui als Shared Layer

### Vorteile der Entkopplung

1. Keine Webapp-Abhängigkeit: Maintenance kann unabhängig gebaut und deployed werden
2. Validiert packages/ui: Erster externer Consumer — beweist Library-Unabhängigkeit
3. Moderner Stack: Vue 3 + Nuxt 4 + Tailwind v4 (Webapp noch Vue 2.7 + Nuxt 2)
4. Einfacher Build: `nuxt generate` → statisches HTML, keine API-Abhängigkeiten

### Spätere Evolution (optional)

Wenn klare Patterns entstehen, können Domain-Packages extrahiert werden:

    Phase 1 (jetzt):  Webapp ist Source of Truth
    Phase 2 (später): Patterns identifizieren
    Phase 3 (später): @ocelot-social/auth, @ocelot-social/posts, etc.

### Entscheidung

# | Datum | Entscheidung
--- | --- | ---
72 | 2026-02-09 | Webapp als Source of Truth für geteilte Business-Komponenten

## 16b. Daten-Entkopplung (ViewModel/Mapper Pattern)

### Problemstellung

Komponenten sind oft direkt an API/GraphQL-Strukturen gekoppelt:

    <!-- ❌ Tight Coupling -->
    <UserCard :user="graphqlResponse.User" />

    // Komponente kennt GraphQL-Struktur
    props.user.avatar.url
    props.user._followedByCurrentUserCount  // Underscore?!
    props.user.__typename                   // Leaked!

Probleme:

- Schema-Änderung = alle Komponenten anpassen
- `__typename`, `_count` etc. leaken in die UI
- Schwer testbar (braucht echte GraphQL-Struktur)
- Komponenten nicht wiederverwendbar

### Lösung: ViewModel + Mapper Pattern

    ┌─────────────────────────────────────────────────────────────┐
    │  GraphQL / API Layer                                        │
    │  • Queries & Mutations                                      │
    │  • Generated Types (graphql-codegen)                        │
    └─────────────────────────────────────────────────────────────┘
                                │
                                ▼
    ┌─────────────────────────────────────────────────────────────┐
    │  Mappers (einziger Ort der API-Struktur kennt)              │
    │  • toUserCardViewModel(graphqlUser) → UserCardViewModel     │
    │  • toPostTeaserViewModel(graphqlPost) → PostTeaserViewModel │
    └─────────────────────────────────────────────────────────────┘
                                │
                                ▼
    ┌─────────────────────────────────────────────────────────────┐
    │  ViewModels (was die UI braucht)                            │
    │  • UserCardViewModel { displayName, avatarUrl, ... }        │
    │  • PostTeaserViewModel { title, excerpt, ... }              │
    └─────────────────────────────────────────────────────────────┘
                                │
                                ▼
    ┌─────────────────────────────────────────────────────────────┐
    │  Presentational Components (kennen nur ViewModels)          │
    │  • <UserCard :user="UserCardViewModel" />                   │
    │  • <PostTeaser :post="PostTeaserViewModel" />               │
    └─────────────────────────────────────────────────────────────┘

### Implementierung

1. ViewModels definieren:

    // types/viewModels.ts
    export interface UserCardViewModel {
      id: string
      displayName: string
      avatarUrl: string | null
      followerCount: number
      isFollowedByMe: boolean
    }

    export interface PostTeaserViewModel {
      id: string
      title: string
      excerpt: string
      authorName: string
      authorAvatarUrl: string | null
      createdAt: Date
      commentCount: number
      canEdit: boolean
    }

2. Mapper-Funktionen:

    // mappers/userMapper.ts
    import type { UserCardViewModel } from '~/types/viewModels'
    import type { UserGraphQL } from '~/graphql/types'

    export function toUserCardViewModel(
      user: UserGraphQL,
      currentUserId?: string
    ): UserCardViewModel {
      return {
        id: user.id,
        displayName: user.name || user.slug || 'Anonymous',
        avatarUrl: user.avatar?.url ?? null,
        followerCount: user._followedByCurrentUserCount ?? 0,
        isFollowedByMe: user.followedByCurrentUser ?? false,
      }
    }

3. Komponenten nutzen nur ViewModels:

    <!-- components/UserCard.vue -->
    <script setup lang="ts">
    import type { UserCardViewModel } from '~/types/viewModels'

    // Komponente kennt NUR das ViewModel, nicht GraphQL
    defineProps<{
      user: UserCardViewModel
    }>()
    </script>

4. Composables kapseln Mapping:

    // composables/useUser.ts
    import { computed } from 'vue'
    import { useQuery } from '@vue/apollo-composable'
    import { GET_USER } from '~/graphql/queries'
    import { toUserCardViewModel } from '~/mappers/userMapper'
    import type { UserCardViewModel } from '~/types/viewModels'

    export function useUser(userId: string) {
      const { result, loading, error } = useQuery(GET_USER, { id: userId })

      const user = computed<UserCardViewModel | null>(() => {
        if (!result.value?.User) return null
        return toUserCardViewModel(result.value.User)
      })

      return { user, loading, error }
    }

### Ordnerstruktur

    webapp/
    ├── graphql/
    │   ├── queries/
    │   ├── mutations/
    │   └── types/              # Generated by graphql-codegen
    ├── types/
    │   └── viewModels.ts       # UI-spezifische Interfaces
    ├── mappers/
    │   ├── userMapper.ts
    │   ├── postMapper.ts
    │   └── index.ts
    ├── lib/
    │   └── composables/
    │       ├── useAuth.ts
    │       ├── useUser.ts      # Gibt ViewModel zurück
    │       └── usePost.ts
    ├── components/             # Presentational (nur ViewModels)
    │   ├── UserCard.vue
    │   └── PostTeaser.vue
    └── pages/                  # Nutzen Composables
        └── users/[id].vue

### Vorteile

Aspekt | Ohne Mapper | Mit Mapper
--- | --- | ---
API-Änderung | Alle Komponenten anpassen | Nur Mapper anpassen
Testing | Mock GraphQL Response | Einfaches ViewModel-Objekt
Wiederverwendung | Komponente an API gebunden | Komponente API-agnostisch
TypeScript | Komplexe/generierte Types | Klare, einfache Interfaces
webapp ↔ maintenance | Verschiedene Strukturen | Gleiche ViewModels

### Regeln

    ┌─────────────────────────────────────────────────────────────┐
    │  Regel 1: Komponenten definieren was sie BRAUCHEN           │
    │           (ViewModel), nicht was die API LIEFERT            │
    ├─────────────────────────────────────────────────────────────┤
    │  Regel 2: Mapper sind der EINZIGE Ort der API kennt         │
    │           API-Änderung = nur Mapper ändern                  │
    ├─────────────────────────────────────────────────────────────┤
    │  Regel 3: Composables kapseln Fetching + Mapping            │
    │           useUser() gibt UserCardViewModel zurück           │
    ├─────────────────────────────────────────────────────────────┤
    │  Regel 4: Presentational Components sind API-agnostisch     │
    │           Einfach testbar, wiederverwendbar                 │
    └─────────────────────────────────────────────────────────────┘

### Entscheidung

# | Datum | Entscheidung
--- | --- | ---
73 | 2026-02-09 | ViewModel/Mapper Pattern für Daten-Entkopplung

## 17. Externe Abhängigkeiten

### Übersicht

Abhängigkeit | Status | Beschreibung
--- | --- | ---
eslint-config-it4c | ⚠️ ESLint 10 ausstehend | v0.8.0 eingebunden, ESLint 10 inkompatibel

### eslint-config-it4c

Status: Funktional, ESLint 10 Update ausstehend

TODO: eslint-config-it4c muss für ESLint 10 aktualisiert werden (aktuell inkompatibel wegen @typescript-eslint/utils).

Lösung: Das Paket wurde in Version 0.8.0 modularisiert und unterstützt jetzt ESLint Flat Config.

Aktuelle Nutzung in @ocelot-social/ui:

    // eslint.config.ts
    import config, { vue3, vitest } from 'eslint-config-it4c'

    export default [
      ...config,    // Base + TypeScript + Prettier + weitere
      ...vue3,      // Vue 3 Regeln
      ...vitest,    // Vitest Test-Regeln
      // Projekt-spezifische Overrides...
    ]

Projekt-spezifische Anpassungen:

- `n/file-extension-in-import`: Ausnahmen für `.vue`, `.css`, `.scss`, `.json`
- `import-x/no-unassigned-import`: CSS-Imports erlaubt
- `vitest/consistent-test-filename`: Pattern `*.spec.ts`
- `vitest/prefer-expect-assertions`: Ausgeschaltet
- `vitest/no-hooks`: Ausgeschaltet
- Example Apps: Eigene ESLint-Configs (ignoriert in Hauptpaket, gelintet im Compatibility-Workflow)

## 18. Kompatibilitätstests (Details)

### Testmatrix

Die Library muss in 4 Kombinationen funktionieren:

                        │  Tailwind         │  CSS (vorkompiliert)
    ────────────────────┼───────────────────┼──────────────────────
    Vue 3.4+            │  ✓ Muss testen    │  ✓ Muss testen
    Vue 2.7             │  ✓ Muss testen    │  ✓ Muss testen

### Werkzeuge & Strategien

#### 1. vue-demi (Code-Ebene)

Abstrahiert Vue 2/3 API-Unterschiede:

    // Komponente importiert von vue-demi, nicht vue
    import { ref, computed, defineComponent } from 'vue-demi'

#### 2. Unit Tests mit Vitest

Hauptpaket testet nur mit Vue 3 (Entwicklungsumgebung). Vue 2 Kompatibilität wird via vue-demi gewährleistet und in Example Apps getestet.

Begründung: Inline Vue 2/3 Matrix verursacht Peer-Dependency-Konflikte.

#### 3. Example Apps (4 Kombinationen) - Hauptstrategie für Vue 2 Tests

    packages/ui/
    ├── examples/
    │   ├── vue3-tailwind/     # Vite + Vue 3 + Tailwind Preset
    │   │   ├── package.json
    │   │   ├── vite.config.ts
    │   │   ├── tailwind.config.js  # Nutzt @ocelot-social/ui/tailwind.preset
    │   │   └── src/App.vue         # Importiert alle Komponenten
    │   │
    │   ├── vue3-css/          # Vite + Vue 3 + style.css
    │   │   ├── package.json
    │   │   ├── vite.config.ts
    │   │   └── src/
    │   │       ├── main.ts         # import '@ocelot-social/ui/style.css'
    │   │       └── App.vue
    │   │
    │   ├── vue2-tailwind/     # Vue CLI / Nuxt 2 + Tailwind
    │   │   └── ...
    │   │
    │   └── vue2-css/          # Vue CLI / Nuxt 2 + style.css
    │       └── ...

Jede Example App:

- Importiert `@ocelot-social/ui` via Workspace-Link
- Rendert alle exportierten Komponenten
- Kann lokal gestartet werden (`npm run dev`)
- Wird in CI gebaut und getestet

#### 4. Playwright E2E Tests

    // e2e/compatibility.spec.ts
    import { test, expect } from '@playwright/test'

    const examples = [
      { name: 'vue3-tailwind', port: 3001 },
      { name: 'vue3-css', port: 3002 },
      { name: 'vue2-tailwind', port: 3003 },
      { name: 'vue2-css', port: 3004 },
    ]

    for (const example of examples) {
      test.describe(example.name, () => {
        test('all components render', async ({ page }) => {
          await page.goto(`http://localhost:${example.port}`)

          // Prüfe dass alle Komponenten sichtbar sind
          await expect(page.locator('[data-testid="os-button"]')).toBeVisible()
          await expect(page.locator('[data-testid="os-card"]')).toBeVisible()
          await expect(page.locator('[data-testid="os-modal"]')).toBeVisible()
        })

        test('styles are applied correctly', async ({ page }) => {
          await page.goto(`http://localhost:${example.port}`)
          const button = page.locator('[data-testid="os-button-primary"]')

          // Prüfe dass CSS korrekt angewendet wird
          await expect(button).toHaveCSS('background-color', /rgb/)
        })

        test('visual regression', async ({ page }) => {
          await page.goto(`http://localhost:${example.port}`)
          await expect(page).toHaveScreenshot(`${example.name}.png`)
        })
      })
    }

#### 5. Package Validation

Tool | Zweck
--- | ---
publint | Prüft package.json auf Export-Fehler
arethetypeswrong | Prüft TypeScript-Typen für alle Entry Points

    {
      "scripts": {
        "check:exports": "publint && attw --pack .",
        "prepublishOnly": "npm run check:exports"
      }
    }

#### 6. package.json Exports (korrekte Struktur)

    {
      "name": "@ocelot-social/ui",
      "type": "module",
      "exports": {
        ".": {
          "import": "./dist/index.mjs",
          "require": "./dist/index.cjs",
          "types": "./dist/index.d.ts"
        },
        "./style.css": "./dist/style.css",
        "./tailwind.preset": {
          "import": "./dist/tailwind.preset.mjs",
          "require": "./dist/tailwind.preset.cjs",
          "types": "./dist/tailwind.preset.d.ts"
        }
      },
      "peerDependencies": {
        "vue": "^2.7.0 || ^3.0.0"
      },
      "peerDependenciesMeta": {
        "tailwindcss": {
          "optional": true
        }
      }
    }

### CI Workflow: Compatibility Matrix

    # .github/workflows/compatibility.yml
    name: Compatibility Tests

    on: [push, pull_request]

    jobs:
      build:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - uses: actions/setup-node@v4
            with:
              node-version: '20'
          - run: npm ci
          - run: npm run build
          - uses: actions/upload-artifact@v4
            with:
              name: dist
              path: packages/ui/dist

      test-unit:
        needs: build
        runs-on: ubuntu-latest
        strategy:
          fail-fast: false
          matrix:
            vue-version: ['2.7', '3.4']
        steps:
          - uses: actions/checkout@v4
          - uses: actions/download-artifact@v4
            with:
              name: dist
              path: packages/ui/dist
          - run: npm ci
          - run: npm test
            env:
              VUE_VERSION: ${{ matrix.vue-version }}

      test-e2e:
        needs: build
        runs-on: ubuntu-latest
        strategy:
          fail-fast: false
          matrix:
            example: ['vue3-tailwind', 'vue3-css', 'vue2-tailwind', 'vue2-css']
        steps:
          - uses: actions/checkout@v4
          - uses: actions/download-artifact@v4
            with:
              name: dist
              path: packages/ui/dist

          - name: Setup example app
            working-directory: packages/ui/examples/${{ matrix.example }}
            run: |
              npm ci
              npm run build

          - name: Start preview server
            working-directory: packages/ui/examples/${{ matrix.example }}
            run: npm run preview &

          - name: Wait for server
            run: npx wait-on http://localhost:4173 --timeout 30000

          - name: Run Playwright tests
            working-directory: packages/ui
            run: npx playwright test --grep "${{ matrix.example }}"

          - uses: actions/upload-artifact@v4
            if: failure()
            with:
              name: playwright-report-${{ matrix.example }}
              path: packages/ui/playwright-report/

      validate-package:
        needs: build
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - uses: actions/download-artifact@v4
            with:
              name: dist
              path: packages/ui/dist
          - run: npm ci
          - name: Validate exports
            run: |
              npx publint packages/ui
              npx @arethetypeswrong/cli packages/ui

### Werkzeug-Übersicht

Werkzeug | Zweck | Phase
--- | --- | ---
vue-demi | Vue 2/3 API-Kompatibilität im Code | Phase 2
Vitest + Matrix | Unit Tests für Vue 2.7 und Vue 3.4 | Phase 2
Example Apps (4x) | Echte Projekte für jede Kombination | Phase 2
Playwright | E2E + Visual Regression für alle 4 | Phase 2
publint | Package.json Export-Validierung | Phase 2
arethetypeswrong | TypeScript Entry Points Check | Phase 2
pkg-pr-new | Preview-Releases in PRs (optional) | Optional

### Checkliste für neue Komponenten

    [ ] Unit Tests laufen mit VUE_VERSION=2.7
    [ ] Unit Tests laufen mit VUE_VERSION=3.4
    [ ] Komponente in allen 4 Example Apps hinzugefügt
    [ ] E2E Tests für Komponente geschrieben
    [ ] Visual Regression Baseline erstellt
    [ ] Keine Vue 3-only APIs verwendet (oder via vue-demi abstrahiert)

## 19. Komplexitätsanalyse

### Umfang nach Phasen

Phase | Aufgaben | Komplexität | Abhängigkeiten
--- | --- | --- | ---
Phase 0: Analyse | 6 Tasks | ✅ Erledigt | -
Phase 1: Vue 2.7 | 6 Tasks | ✅ Erledigt | -
Phase 2: Setup | 26 Tasks | Mittel | ⚠️ eslint-config-it4c (extern)
Phase 3: Tokens | 6 Tasks | Niedrig | Keine externen
Phase 4: Migration | 15 Komponenten | Hoch | Pro Komponente: Spec→Dev→Test→Integrate
Phase 5: Finalisierung | 7 Tasks | Niedrig | Alle vorherigen Phasen

### Bekannte Risikofaktoren

Risiko | Beschreibung | Auswirkung | Mitigation
--- | --- | --- | ---
eslint-config-it4c | Externes Projekt muss zuerst modularisiert werden | Blockiert Linting-Setup in Phase 2 | Temporäre lokale ESLint-Config als Workaround
vue-demi Kompatibilität | Unbekannte Edge-Cases bei Vue 2/3 Dual-Support | Unerwartete Bugs bei Integration | Frühzeitig in Example Apps testen
Visual Regression Baselines | Können bei Design-Änderungen viel Nacharbeit erfordern | Zusätzlicher Aufwand bei Änderungen | Baselines erst nach Design-Freeze erstellen
Feature-Parity | Alte Komponenten haben undokumentierte Verhaltensweisen | Regressions bei Migration | Gründliche Analyse vor Implementierung
Tailwind + CSS Dual-Build | Komplexe Build-Konfiguration | Build-Fehler, Inkonsistenzen | Früh beide Varianten parallel testen

### Parallelisierbarkeit

Phase | Parallelisierbar | Details
--- | --- | ---
Phase 2 | Teilweise | Die meisten Tasks sind sequentiell (Setup-Reihenfolge wichtig)
Phase 3 | Nein | Token-Ebenen bauen aufeinander auf (Base → Semantic → Component)
Phase 4 | Ja (nach Tier 1) | Tier 2/3 Komponenten können parallel entwickelt werden
Phase 5 | Teilweise | Dokumentation kann parallel zur letzten Integration

Parallelisierbare Aufgaben in Phase 2:

- 4 Example Apps (vue3-tailwind, vue3-css, vue2-tailwind, vue2-css)
- GitHub Workflows (unabhängig voneinander)
- LICENSE, README.md, CONTRIBUTING.md

Sequentielle Abhängigkeiten in Phase 2:

1. Vite + Vue 3 Projekt → vue-demi → Tailwind → Build-Pipeline
2. Vitest → Tests → Visual Regression
3. Package-Struktur → release-please → npm Publish Workflow

### Aufwandstreiber pro Komponente (Phase 4)

Jede Komponente durchläuft:

    ┌─────────────┐   ┌─────────────┐   ┌─────────────┐   ┌─────────────┐   ┌─────────────┐
    │  ANALYSE    │ → │    SPEC     │ → │   DEVELOP   │ → │     QA      │ → │  INTEGRATE  │
    ├─────────────┤   ├─────────────┤   ├─────────────┤   ├─────────────┤   ├─────────────┤
    │ Bestehende  │   │ Props       │   │ Vue 3 Code  │   │ Unit Tests  │   │ In Webapp   │
    │ Varianten   │   │ Events      │   │ TypeScript  │   │ Vue 2 Tests │   │ einbinden   │
    │ analysieren │   │ Slots       │   │ Tailwind    │   │ A11y Tests  │   │             │
    │             │   │ Tokens      │   │ Storybook    │   │ Visual Reg. │   │ Alte Komp.  │
    │             │   │ A11y        │   │ Stories     │   │ 4 Examples  │   │ entfernen   │
    └─────────────┘   └─────────────┘   └─────────────┘   └─────────────┘   └─────────────┘

Aufwand variiert stark nach Komponente:

Komponente | Komplexität | Grund
--- | --- | ---
OsIcon | Niedrig | Einfache Wrapper-Komponente
OsSpinner | Niedrig | Nur Animation + Größen
OsButton | Hoch | Viele Varianten, Link-Support, States
OsCard | Niedrig | Einfaches Layout
OsModal | Hoch ✅ | Focus-Trap, Scroll-Lock, Body Overflow Save/Restore, Vue 2/3 h() Compat, A11y (dialog, aria-modal, aria-labelledby), ESC-Key, Backdrop-Click, Scrollable Content tabindex
OsDropdown | Hoch | Positioning, Click-Outside, Hover-States
OsInput | Mittel | Validierung, States, Icons
OsAvatar | Niedrig | Bild + Fallback