117 KiB
@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
| # | Abschnitt |
|---|---|
| 1 | Projektziel & Vision |
| # | Abschnitt |
|---|---|
| 2 | Tech-Stack |
| 3 | Build & Distribution |
| 4 | Icon-Architektur |
| 5 | Design-Token & Branding |
| # | Abschnitt |
|---|---|
| 6 | CI/CD & Release |
| 7 | Dokumentation & DX |
| 8 | Migrationsstrategie |
| 9 | Dokumentationsstrategie |
| 10 | Kompatibilitätstests |
| # | Abschnitt |
|---|---|
| 11 | Entscheidungen |
| 12 | Arbeitsprotokoll |
| 13 | Komponenten-Katalog |
| 14 | Ressourcen & Links |
| 15 | Dokumentationsstrategie (Details) |
| # | 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: █████████░ 83% (20/24 Aufgaben) - Webapp-Integration (M4a ✅, M5 ✅)
Phase 4: █░░░░░░░░░ 6% (1/17 Aufgaben) - OsButton ✅
Phase 5: ░░░░░░░░░░ 0% (0/7 Aufgaben)
───────────────────────────────────────
Gesamt: ███████░░░ 69% (59/86 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: ~90 Buttons in Webapp
├─ Migriert: 32 Buttons (36%) ✅
├─ Ohne neue Props: 0 Buttons (Milestone 4a ✅)
└─ Mit icon/circle/loading: ~60 Buttons (Milestone 4c)
OsButton Features:
├─ variant: ✅ primary, secondary, danger, warning, success, info, default
├─ appearance: ✅ filled, outline, ghost
├─ size: ✅ xs, sm, md, lg, xl
├─ disabled: ✅ mit hover/active-Override
├─ icon: ⬜ TODO (Milestone 4b)
├─ circle: ⬜ TODO (Milestone 4b)
└─ loading: ⬜ TODO (Milestone 4b)
Aktueller Stand
Letzte Aktualisierung: 2026-02-10 (Session 12)
Aktuelle Phase: Phase 3 (Webapp-Integration) - Milestone 4a abgeschlossen ✅ (32 Buttons migriert, nächster: Milestone 4b)
Zuletzt abgeschlossen:
- Projektordner erstellt
- Planungsdokument erstellt
- Tech-Stack entschieden
- Branding-Architektur definiert
- Migrationsstrategie definiert
- Phase 0: Komponenten-Analyse (177 Komponenten katalogisiert)
- Button-Familie detailiert analysiert (Props, Styles, Konsolidierung)
- Modal-Familie detailiert analysiert (Architektur erkannt)
- Menu-Familie detailiert analysiert (3 Patterns identifiziert)
- Priorisierung erstellt (15 Komponenten in 4 Tiers)
- Konsolidierungsplan finalisiert
- Phase 1: Vue 2.7 Upgrade ✅
- Vue 2.6.14 → 2.7.16
- vue-template-compiler entfernt
- @vue/composition-api entfernt
- @nuxtjs/composition-api entfernt
- Webpack-Alias für @vue/composition-api → vue
- Webpack-Regel für ESM .mjs Module
- Unit-Tests: 157 Suites, 979 passed, 87 Snapshots ✅
- Integrationstests: bestanden ✅
- Phase 2: Projekt-Setup ✅
- Vite + Vue 3 Projekt initialisiert
- vue-demi für Vue 2/3 Kompatibilität
- Vitest konfiguriert (integriert in vite.config.ts)
- npm Package-Struktur mit korrekten exports
- README.md Grundgerüst
- LICENSE (Apache 2.0)
- Plugin-Tests geschrieben
- Tailwind CSS v4 mit @tailwindcss/vite
- Dual-Build (style.css + tailwind.preset)
- Dark Mode Grundstruktur (via Tailwind dark: Prefix)
- Prop-Types definiert (Size, Rounded, Shadow, Variant)
- Branding-Architektur (keine Defaults, validateCssVariables)
- eslint-config-it4c eingerichtet (v0.8.0)
- ESLint Flat Config mit Vue 3 + Vitest Modulen
- Prettier-Integration via eslint-plugin-prettier
- GitHub Workflows (ui-lint.yml, ui-test.yml, ui-build.yml)
- 100% Test-Coverage Requirement
- .tool-versions (Node 25.5.0, konsistent mit Dockerfiles)
- Example Apps für Kompatibilitätstests (4er-Matrix)
- GitHub Workflow ui-compatibility.yml für Vue 2/3 Tests (inkl. Lint)
- Eigene ESLint + Prettier Configs für Example Apps
- Type Assertions für CI-Kompatibilität (
as unknown as Plugin) - Bundle Size Check (size-limit) mit ui-size.yml Workflow
- Package-Validierung (publint, arethetypeswrong) mit CJS/ESM Types
- Kompatibilitätstest-Workflow mit 4 Example Apps (Vue 2/3 × Tailwind/CSS)
- release-please Manifest-Konfiguration (Monorepo-Setup)
- npm Publish Workflow (ui-release.yml)
- CONTRIBUTING.md (Entwickler-Leitfaden)
- Dependabot für UI-Package und Example Apps konfiguriert
- CSS-Build separat via Tailwind CLI (closeBundle Hook)
- CVA (class-variance-authority) für typsichere Varianten
- cn() Utility für Tailwind-Klassen-Merge (clsx + tailwind-merge)
- OsButton Komponente mit CVA-Varianten implementiert
- ESLint-Konfiguration angepasst (vue/max-attributes-per-line, import-x/no-relative-parent-imports)
- Storybook 10 für Dokumentation eingerichtet (Wasserfarben-Theme)
- OsButton.stories.ts mit Playground + allen Varianten/Appearances/Sizes
- Storybook Build-Konfiguration (viteFinal entfernt Library-Plugins)
- Docker Setup (Dockerfile, docker-compose, ui-docker.yml)
- Visual Regression Tests (Playwright, colocated) mit integriertem A11y-Check
- Completeness Check (verify Script prüft Story, Visual, checkA11y, Keyboard, Varianten)
- ESLint Plugins: vuejs-accessibility, playwright, storybook, jsdoc
Aktuell in Arbeit:
- Phase 3, Milestone 4b: icon/circle/loading Props in OsButton implementieren
- Phase 3, Milestone 4c: ~60 Buttons mit icon/circle/loading migrieren
Zuletzt abgeschlossen (Session 12 - CSS-Linting, CI-Optimierung, Code-Review Fixes):
- CSS-Linting:
@eslint/css+tailwind-csstreefür Tailwind v4 Syntax-Support excludeCSS()Helper: JS-Regeln von CSS-Dateien fernhalten (language-Inkompatibilität)- CSS-Regeln:
no-empty-blocks,no-duplicate-imports,no-invalid-at-rules - CI-Workflow-Trigger optimiert: 9 UI-Workflows von
on: pushauf Branch+Path-Filter (master,packages/ui/**) custom-class→classMigration: 4 Stellen in 3 Webapp-Dateien (notifications, MapStylesButtons, EmbedComponent)- Vue 3 Template-Fix:
this.$t()→$t()in CommentCard.vue (Zeile 5 + 58) - Pre-existing Fix:
asyncArrow-Function in OsButton.visual.spec.ts
Zuvor abgeschlossen (Session 11 - Storybook & Code-Review Fixes):
- Wasserfarben-Farbschema für Storybook (Ultramarin, Dioxazin-Violett, Alizarin, Ocker, Viridian, Cöruleum)
- Stories erweitert: Playground (interaktive Controls), alle Varianten in allen Stories
- Einzelne Stories (Primary, Secondary, Danger, Default) durch AllVariants ersetzt
- AllAppearances zeigt alle 7 Varianten × 3 Appearances
- Einheitlicher Border (0.8px) über alle Appearances (kein Layout-Shift mehr)
- WCAG 2.4.7 Fix: Default-Variante hat jetzt
focus:outline-dashed focus:outline-current - Keyboard Accessibility Test: prüft Focus-Indikator auf allen Buttons im Browser
data-appearanceAttribut: robuste CSS-Selektoren statt fragile escaped Tailwind-Klassen- Code-Review Feedback eingearbeitet (Unit-Tests, Testnamen, CSS-Selektoren)
Zuvor abgeschlossen (Milestone 5 + Analyse):
- Visuelle Validierung: 16/16 Buttons validiert ✅
- OsButton Features:
appearance(outline, ghost),xssize, focus/active states - Disabled-Styles: CSS-Variablen, hover/active-Override, Border-Fix
- Codebase-Analyse: 14 weitere migrierbare Buttons identifiziert (Scope: 16/35)
Zuletzt erledigt (Phase 3):
- vue-demi zur Webapp hinzugefügt (Vue 2.7 Kompatibilität)
- Webpack-Alias für vue-demi (nutzt Webapp's Vue 2.7 statt UI-Library's Vue 3)
- Webpack-Alias für @ocelot-social/ui (dist Pfade mit $ für exakten Match)
- OsButton mit isVue2 Render-Funktion (Vue 2: attrs-Objekt, Vue 3: flat props)
- CSS-Reihenfolge angepasst (UI-Library nach Styleguide für korrekte Spezifität)
- Manueller visueller Vergleich ✅
- Jest-Integration für vue-demi ✅
- Custom Mock (
test/__mocks__/@ocelot-social/ui.js) statt direktem Import - Problem: Jest's moduleNameMapper greift nicht für verschachtelte requires in CJS
- Problem: Jest lädt
vue.runtime.common.jsmit exports unterdefault - Lösung: Module._load Patch für vue-demi + defineComponent von Vue.default
- Setup-File (
test/vueDemiSetup.js) für Module._resolveFilename Patch - 979 Tests bestehen ✅
- Custom Mock (
- Button-Variants an ds-button angepasst (font-semibold, rounded, box-shadow)
- UserTeaserPopover.vue migriert (verwendet
<os-button>) - Docker Build für UI-Library ✅
- ui-library Stage in Dockerfile + Dockerfile.maintenance
- COPY --from=ui-library ./app/ /packages/ui/
- CI-Kompatibilität ✅
- Relativer Pfad
file:../packages/uistatt absolut/packages/ui - Funktioniert lokal, in CI und in Docker
- Relativer Pfad
- OsButton attrs/listeners Forwarding ✅
- getCurrentInstance() für $listeners Zugriff in Vue 2
- inheritAttrs: false für manuelle Weiterleitung
- Jest Mock um alle Composition API Funktionen erweitert
- 16 Buttons migriert (ohne icon/circle/loading) ✅
- GroupForm.vue, EmbedComponent.vue, DonationInfo.vue, CommentCard.vue
- MapStylesButtons.vue, GroupMember.vue, embeds.vue
- notifications.vue, privacy.vue, terms-and-conditions-confirm.vue, UserTeaserPopover.vue
- Disabled-Styles korrigiert ✅
- CSS-Variablen
--color-disabledund--color-disabled-contrasthinzugefügt - Filled-Buttons: Grauer Hintergrund statt opacity (wie buttonStates Mixin)
- Outline/Ghost: Graue Border/Text
- CSS-Variablen
- terms-and-conditions-confirm.vue: Read T&C Button →
appearance="outline" variant="primary" - Disabled:active/hover Spezifität ✅
- CSS-Regeln in index.css mit höherer Spezifität für disabled:hover und disabled:active
- Button zeigt sofort disabled-Farben, auch wenn während :active disabled wird
- notifications.vue: Check All + Uncheck All →
appearance="outline" variant="primary" - embeds.vue: Allow All →
appearance="outline" variant="primary" - Disabled Border-Fix ✅
- CSS-Regeln in index.css:
border-style: solidundborder-width: 0.8pxbei disabled - Verhindert Layout-Sprung wenn Button disabled wird
- CSS-Regeln in index.css:
Nächste Schritte:
Phase 0: Komponenten-Analyse✅Phase 1: Vue 2.7 Upgrade✅Phase 2: Projekt-Setup✅ ABGESCHLOSSEN- Phase 3: Webapp-Integration - 32/90 Buttons migriert (36%)
- yarn link / Webpack-Alias in Webapp
- CSS-Variablen definieren (ocelot-ui-variables.scss)
- 16 Buttons migriert & validiert ✅
- Docker Build + CI-Kompatibilität
- Milestone 4a: 14 weitere Buttons (ohne neue Props) ✅
- Milestone 4b: icon/circle/loading Props implementieren
- Milestone 4c: ~60 Buttons mit icon/circle/loading migrieren
Manuelle Setup-Aufgaben (außerhalb Code):
NPM_TOKENals 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 ✅
- Vollständige Katalogisierung Webapp-Komponenten (139 Komponenten)
- Vollständige Katalogisierung Styleguide-Komponenten (38 Komponenten)
- Duplikate identifizieren und dokumentieren (5 direkte + 3 Familien)
- Inkonsistenzen und Probleme erfassen (Button/Modal/Menu analysiert)
- Konsolidierungsplan erstellen (Token-Liste)
- Priorisierung der zu migrierenden Komponenten (15 Komponenten in 4 Tiers)
Phase 1: Vue 2.7 Upgrade ✅
- Vue 2.6 → Vue 2.7 Upgrade in Webapp (2.6.14 → 2.7.16)
- Abhängigkeiten aktualisieren:
- vue-template-compiler entfernt (in Vue 2.7 eingebaut)
- @vue/composition-api entfernt (in Vue 2.7 eingebaut)
- @nuxtjs/composition-api entfernt (nicht mehr nötig)
- vue-server-renderer auf 2.7.16 aktualisiert
- Tests durchführen: 157 Suites, 979 passed, 87 Snapshots ✅
- Regressionstests (
yarn devund manuelle Prüfung) ✅
Phase 2: Projekt-Setup ✅
- Vite + Vue 3 Projekt initialisieren
- vue-demi einrichten für Vue 2 Kompatibilität
- Tailwind CSS einrichten (v4 mit @tailwindcss/vite)
- Dual-Build konfigurieren (Tailwind Preset + vorkompilierte CSS)
- CSS Custom Properties Token-System aufsetzen (requiredCssVariables + validateCssVariables)
- Dark Mode Grundstruktur (via Tailwind
dark:Prefix, dokumentiert) - Storybook für Dokumentation einrichten
- Vitest konfigurieren
- eslint-config-it4c einrichten (v0.8.0: TypeScript, Vue 3, Vitest, Prettier)
- npm Package-Struktur (@ocelot-social/ui) mit korrekten exports
- Vue 2/3 Kompatibilitätstests (via Example Apps)
- GitHub Workflows einrichten (ui-lint.yml, ui-test.yml, ui-build.yml, ui-compatibility.yml)
- Docker Setup (Dockerfile, docker-compose, ui-docker.yml Workflow)
- Visual Regression Tests einrichten (Playwright, colocated mit Komponenten)
- Accessibility Tests in Visual Tests integriert (@axe-core/playwright)
- Keyboard Accessibility Tests (describe('keyboard accessibility'))
- ESLint Plugins: vuejs-accessibility, playwright, storybook, jsdoc, @eslint/css
- Bundle Size Check einrichten (size-limit, ui-size.yml)
- Package-Validierung einrichten (publint, arethetypeswrong)
- Example Apps erstellen (vue3-tailwind, vue3-css, vue2-tailwind, vue2-css)
- Kompatibilitätstest-Workflow einrichten (4er-Matrix, siehe §18)
- release-please Manifest-Konfiguration
- npm Publish Workflow (ui-release.yml)
- Storybook Build Workflow (ui-storybook.yml)
- LICENSE Datei (Apache 2.0)
- README.md Grundgerüst (Installation, Quick Start, Struktur)
- CONTRIBUTING.md
- 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 ✅
- @ocelot-social/ui in Webapp installieren (yarn link + Webpack-Alias)
- vue-demi zur Webapp hinzugefügt (für Vue 2.7 Kompatibilität)
- Webpack-Alias für vue-demi (nutzt Webapp's Vue 2.7)
- Webpack-Alias für @ocelot-social/ui$ und style.css$
- CSS Custom Properties in Webapp definieren (ocelot-ui-variables.scss)
- CSS-Reihenfolge angepasst (UI-Library nach Styleguide)
- Import-Pfade testen
- Docker Build Stage für UI-Library (Dockerfile + Dockerfile.maintenance)
- Relativer Pfad für CI-Kompatibilität (file:../packages/ui)
- Jest Mock für @ocelot-social/ui (test/mocks/@ocelot-social/ui.js)
Milestone 2: Erste Integration (Minimaler Aufwand) ✅
- OsButton mit isVue2 Render-Funktion (Vue 2/3 kompatibel)
- Button-Variants an ds-button angepasst (font-semibold, rounded, box-shadow)
- OsButton in UserTeaserPopover.vue eingesetzt (
variant="primary") - Manueller visueller Vergleich ✅
- Webapp-Tests bestehen ✅ (979 Tests, jest moduleNameMapper für vue-demi)
Milestone 3: Schrittweise Erweiterung ✅
- GroupForm.vue Cancel-Button migriert
- OsButton attrs/listeners Forwarding (Vue 2 $listeners via getCurrentInstance)
- 14 weitere Buttons migriert (alle ohne icon/circle/loading)
Milestone 4a: Weitere Buttons migrieren (14 ohne neue Props)
- Modal Cancel-Buttons (DisableModal, DeleteUserModal, ReleaseModal)
- Form Cancel/Submit-Buttons (ContributionForm, EnterNonce, MySomethingList)
- ImageUploader.vue (2× Crop-Buttons)
- Page-Buttons (donations, badges, notifications/index, profile Unblock/Unmute)
- ReportRow.vue More-Details-Button
Milestone 4b: OsButton Props erweitern
iconProp implementieren (slot-basiert oder Icon-Komponente)circleVariant zu CVA hinzufügenloadingProp mit Spinner implementieren
Milestone 4c: Buttons mit icon/circle/loading migrieren (~60 Buttons)
Button-Komponenten (Wrapper):
- Button/JoinLeaveButton.vue (icon, loading)
- Button/FollowButton.vue (icon, loading)
- LoginButton/LoginButton.vue (icon, circle)
- InviteButton/InviteButton.vue (icon, circle)
- EmotionButton/EmotionButton.vue (circle)
- CustomButton/CustomButton.vue (2× circle)
- LabeledButton/LabeledButton.vue (icon, circle)
Navigation & Menus:
- ContentMenu/ContentMenu.vue (icon, circle)
- ContentMenu/GroupContentMenu.vue (icon, circle)
- ChatNotificationMenu.vue (circle)
- NotificationMenu.vue (3× icon, circle)
- HeaderMenu/HeaderMenu.vue (icon, circle)
- Map/MapButton.vue (circle)
Editor:
- Editor/MenuBarButton.vue (icon, circle)
- Editor/MenuLegend.vue (~10× icon, circle)
Filter & Input:
- HashtagsFilter.vue (icon, circle)
- CategoriesSelect.vue (icon)
- SearchableInput.vue (icon, circle)
- Select/LocationSelect.vue (icon)
- PaginationButtons.vue (2× icon, circle)
Chat:
- Chat/Chat.vue (2× icon, circle)
- Chat/AddChatRoomByUserSearch.vue (icon, circle)
Forms & Auth:
- LoginForm/LoginForm.vue (icon, loading)
- PasswordReset/Request.vue (loading)
- PasswordReset/ChangePassword.vue (loading)
- Password/Change.vue (loading)
- ContributionForm.vue Submit (icon, loading)
- GroupForm.vue Submit (icon)
- CommentForm/CommentForm.vue (loading)
Modals:
- Modal/ConfirmModal.vue (2× icon, loading)
- Modal/ReportModal.vue (2× icon, loading)
- Modal/DisableModal.vue Confirm (icon)
- Modal/DeleteUserModal.vue Confirm (icon)
- Modal/ReleaseModal.vue Confirm (icon)
Features:
- ComponentSlider.vue (2× icon, loading)
- MySomethingList.vue (3× icon, circle, loading)
- CreateInvitation.vue (icon, circle)
- Invitation.vue (2× icon, circle)
- ProfileList.vue (loading)
- ReportRow.vue Confirm (icon)
- ImageUploader.vue Delete/Cancel (2× icon, circle)
- CommentCard.vue Reply (icon, circle)
- EmbedComponent.vue Close (icon, circle)
- CtaUnblockAuthor.vue (icon)
- data-download.vue (icon, loading)
Pages:
- pages/groups/_id/_slug.vue (3× icon, circle, loading)
- pages/admin/users/index.vue (2× icon, circle, loading)
- pages/settings/index.vue (icon, loading)
- pages/settings/blocked-users.vue (icon, circle)
- pages/settings/muted-users.vue (icon, circle)
- pages/settings/my-email-address/*.vue (2× icon)
- pages/profile/_id/_slug.vue Chat (icon)
- pages/post/_id/_slug/index.vue (icon, circle)
Milestone 5: Validierung & Dokumentation ✅
- Keine visuellen Änderungen bestätigt (16/16 Buttons validiert)
- Keine funktionalen Änderungen bestätigt
- Disabled-Styles korrigiert (hover/active-Override, Border-Fix)
- Webapp-Tests bestehen weiterhin (TODO: Regressionstest)
- Erkenntnisse in KATALOG.md dokumentiert
Einsatzstellen-Übersicht:
| Kategorie | Buttons | Status |
|---|---|---|
| ✅ Migriert & Validiert | 24 | Erledigt |
| ⏳ Ohne neue Props (M4a) | 6 | In Arbeit (8 von 14 erledigt) |
| ⬜ Mit icon/circle/loading (M4c) | ~60 | Ausstehend |
| Gesamt | ~90 | 27% 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 (15 Komponenten + 2 Infrastruktur)
Tier 1: Kern-Komponenten
- OsIcon (vereint DsIcon + BaseIcon)
- OsSpinner (vereint DsSpinner + LoadingSpinner)
- OsButton (vereint DsButton + BaseButton) ✅ Entwickelt in Phase 2
- OsCard (vereint DsCard + BaseCard)
Tier 2: Layout & Feedback
- OsModal (Basis: DsModal)
- OsDropdown (Basis: Webapp Dropdown)
- OsAvatar (vereint DsAvatar + ProfileAvatar)
- OsInput (Basis: DsInput)
Tier 3: Navigation & Typography
- OsMenu (Basis: DsMenu)
- OsMenuItem (Basis: DsMenuItem)
- OsHeading (Basis: DsHeading)
- OsText (Basis: DsText)
Tier 4: Spezial-Komponenten
- OsSelect
- OsTable
- OsTag
Infrastruktur
- System-Icons einrichten
- CI docs-check Workflow (JSDoc-Coverage, README-Aktualität)
Phase 5: Finalisierung
- 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
styleguideOrdner 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 (vollständig) | xs, sm, md, lg, xl, 2xl |
| 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) │
├─────────────────────────────────────────────────────────────┤
│ icons/system/ # ~10 System-Icons │
│ ├── close.svg # Modal, Chip, Dialoge │
│ ├── check.svg # Modal confirm, Checkboxen │
│ ├── chevron-down.svg # Select, Dropdown │
│ ├── chevron-up.svg # Select, Accordion │
│ ├── spinner.svg # Loading-States │
│ ├── bars.svg # Hamburger-Menu │
│ ├── copy.svg # CopyField │
│ ├── eye.svg # Password-Toggle │
│ ├── eye-slash.svg # Password-Toggle, Anonym │
│ └── search.svg # Search-Input │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ Webapp / Konsumierendes Projekt │
├─────────────────────────────────────────────────────────────┤
│ assets/icons/ # Feature-Icons (beliebig viele) │
│ ├── user.svg │
│ ├── bell.svg │
│ ├── heart.svg │
│ ├── settings.svg │
│ └── ... │
└─────────────────────────────────────────────────────────────┘
Begründung
- Library muss standalone funktionieren - Ein OsModal braucht einen Close-Button ohne zusätzliche Konfiguration
- 616 Icons sind zu viel - Der Styleguide hat 616 Icons, die meisten werden nie gebraucht
- Feature-Icons gehören zur App - Icons wie
user,bell,heartsind Business-Logik - Branding-Flexibilität - Verschiedene Ocelot-Instanzen können unterschiedliche Icon-Sets verwenden
System-Icons (in Library enthalten)
| Icon | Verwendung in Komponenten |
|---|---|
close |
OsModal, OsChip, OsDialog, OsAlert |
check |
OsModal (confirm), OsCheckbox |
chevron-down |
OsSelect, OsDropdown, OsAccordion |
chevron-up |
OsSelect, OsAccordion |
spinner |
OsButton (loading), OsSpinner |
bars |
OsPage (mobile menu) |
copy |
OsCopyField |
eye |
OsInput (password toggle) |
eye-slash |
OsInput (password toggle), OsAvatar (anonym) |
search |
OsInput (search variant) |
API-Design
// OsIcon akzeptiert verschiedene Formate:
// 1. System-Icon (String) - aus Library
<OsIcon name="close" />
// 2. Vue-Komponente - für App-Icons
<OsIcon :icon="UserIcon" />
// 3. In Komponenten mit icon-Prop
<OsButton icon="close" /> // System-Icon
<OsButton :icon="CustomIcon" /> // Komponente
Webapp-Integration
// webapp/plugins/icons.ts
import { provideIcons } from '@ocelot-social/ui'
import * as appIcons from '~/assets/icons'
export default defineNuxtPlugin((nuxtApp) => {
// App-Icons global registrieren
provideIcons(appIcons)
})
<!-- Dann in der Webapp nutzbar -->
<OsButton :icon="icons.user" />
<OsIcon :icon="icons.bell" />
Aktuelle Icon-Statistik
| Quelle | Anzahl | Status |
|---|---|---|
| Styleguide (_all) | 616 | Nicht übernehmen (FontAwesome 4 komplett) |
| Webapp (svgs) | 238 | Feature-Icons, bleiben in Webapp |
| Library (system) | ~10 | Nur essenzielle System-Icons |
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- Versionenrelease-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):
- GitHub sendet Webhook bei Release-Event
- Server empfängt Webhook
- Server führt
scripts/deploy-histoire.shaus (Teil des Repos) - Script: git pull → npm ci → histoire 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-histoire | 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
histoire 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
70 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 (xs, sm, md, lg, xl, 2xl) | Konsistenz mit Tailwind, intuitive Benennung |
| 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 |
| 69 | Webapp ↔ Maintenance Sharing | Webapp als Source of Truth | Kein separates "shared" Package, maintenance importiert aus webapp/ (siehe §16a) |
| 70 | 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-*-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) |
13. Komponenten-Katalog
Detaillierte Katalogisierung in separater Datei: KATALOG.md
Zusammenfassung (aus KATALOG.md)
| Quelle | Gesamt | Analysiert | Duplikate | Zu migrieren |
|---|---|---|---|---|
| Webapp | 139 | ✅ | 5 | Priorisiert |
| Styleguide | 38 | ✅ | 5 | Priorisiert |
| Gesamt | 177 | ✅ | 5 direkte + 3 Familien | 15 Kern-Komponenten |
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:
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 → Webapp Wenn 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
Die Webapp und Maintenance-App sind aktuell verschachtelt und sollen getrennt werden. Einige Business-Komponenten werden in beiden Apps benötigt, gehören aber nicht in die UI-Library.
Das DX-Problem: "shared" hat kein logisches Kriterium außer "wird in beiden gebraucht".
Analysierte Optionen
| Option | Beschreibung | Bewertung |
|---|---|---|
| A: Domain Packages | @ocelot-social/auth, @ocelot-social/posts, etc. |
Gut bei vielen Komponenten, aber Overhead |
| B: Core + Duplikation | Composables teilen, Komponenten duplizieren | Gut wenn UI unterschiedlich |
| C: Webapp als Source | Maintenance importiert aus Webapp | Einfachste Lösung |
Empfehlung: Option C (Webapp als Source of Truth)
┌─────────────────────────────────────────────────────────────┐
│ @ocelot-social/ui │
│ ───────────────── │
│ • OsButton, OsModal, OsCard, OsInput │
│ • Rein präsentational, keine Abhängigkeiten │
├─────────────────────────────────────────────────────────────┤
│ webapp/ │
│ ─────── │
│ • Alle Business-Komponenten (Source of Truth) │
│ • Composables in webapp/lib/composables/ │
│ • GraphQL in webapp/graphql/ │
│ • Ist die "Haupt-App" │
├─────────────────────────────────────────────────────────────┤
│ maintenance/ │
│ ──────────── │
│ • Importiert aus @ocelot-social/ui │
│ • Importiert aus webapp/ via Alias │
│ • Nur maintenance-spezifische Komponenten lokal │
└─────────────────────────────────────────────────────────────┘
Umsetzung
maintenance/nuxt.config.js:
export default {
alias: {
'@webapp': '../webapp',
'@ocelot-social/ui': '../packages/ui/dist'
}
}
Import in Maintenance:
// UI-Komponenten aus Library
import { OsButton, OsModal } from '@ocelot-social/ui'
// Business-Komponenten aus Webapp
import FollowButton from '@webapp/components/FollowButton.vue'
import PostTeaser from '@webapp/components/PostTeaser.vue'
// Composables aus Webapp
import { useAuth } from '@webapp/lib/composables/useAuth'
import { useFollow } from '@webapp/lib/composables/useFollow'
Kriterien für Entwickler
| Frage | Antwort |
|---|---|
| Wo suche ich eine UI-Komponente? | @ocelot-social/ui |
| Wo suche ich eine Business-Komponente? | webapp/components/ |
| Wo erstelle ich eine neue geteilte Komponente? | webapp/components/ |
| Wo erstelle ich maintenance-spezifische Komponenten? | maintenance/components/ |
Vorteile
- Klare Regel: Alles Business-bezogene ist in Webapp
- Kein neues Package: Weniger Overhead
- Eine Source of Truth: Keine Sync-Probleme
- Einfache Migration: Später ggf. Domain-Packages extrahieren
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 |
|---|---|---|
| 68 | 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,_countetc. 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 |
|---|---|---|
| 70 | 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,.jsonimport-x/no-unassigned-import: CSS-Imports erlaubtvitest/consistent-test-filename: Pattern*.spec.tsvitest/prefer-expect-assertions: Ausgeschaltetvitest/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/uivia 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:
- Vite + Vue 3 Projekt → vue-demi → Tailwind → Build-Pipeline
- Vitest → Tests → Visual Regression
- 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 | Teleport, Focus-Trap, Animations, A11y |
| OsDropdown | Hoch | Positioning, Click-Outside, Hover-States |
| OsInput | Mittel | Validierung, States, Icons |
| OsAvatar | Niedrig | Bild + Fallback |