mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2026-03-01 12:44:37 +00:00
959 lines
446 KiB
HTML
959 lines
446 KiB
HTML
<!doctype html>
|
||
<html lang="en-US" data-theme="light">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||
<meta name="generator" content="VuePress 2.0.0-rc.18" />
|
||
<meta name="theme" content="VuePress Theme Hope 2.0.0-rc.59" />
|
||
<style>
|
||
:root {
|
||
--vp-c-bg: #fff;
|
||
}
|
||
|
||
[data-theme="dark"] {
|
||
--vp-c-bg: #1b1b1f;
|
||
}
|
||
|
||
html,
|
||
body {
|
||
background: var(--vp-c-bg);
|
||
}
|
||
</style>
|
||
<script>
|
||
const userMode = localStorage.getItem("vuepress-theme-hope-scheme");
|
||
const systemDarkMode =
|
||
window.matchMedia &&
|
||
window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||
|
||
if (userMode === "dark" || (userMode !== "light" && systemDarkMode)) {
|
||
document.documentElement.setAttribute("data-theme", "dark");
|
||
}
|
||
</script>
|
||
<meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/favicon.ico"><title>@ocelot-social/ui - Projektdokumentation | Ocelot.Social Documentation</title><meta name="description" content="Ocelot.Social Documentation">
|
||
<link rel="preload" href="/assets/style-B3yxn0TC.css" as="style"><link rel="stylesheet" href="/assets/style-B3yxn0TC.css">
|
||
<link rel="modulepreload" href="/assets/app-C6w_Pklu.js"><link rel="modulepreload" href="/assets/PROJEKT.html-BZRNQpP2.js"><link rel="modulepreload" href="/assets/plugin-vue_export-helper-DlAUqK2U.js">
|
||
<link rel="prefetch" href="/assets/CHANGELOG.html-Bnklulr8.js" as="script"><link rel="prefetch" href="/assets/CODE_OF_CONDUCT.html-D7Z-kzHo.js" as="script"><link rel="prefetch" href="/assets/CONTRIBUTING.html-DoGQ344g.js" as="script"><link rel="prefetch" href="/assets/LICENSE.html-BkNHjkmj.js" as="script"><link rel="prefetch" href="/assets/index.html-1dvphctW.js" as="script"><link rel="prefetch" href="/assets/SUMMARY.html-DlsPW9yB.js" as="script"><link rel="prefetch" href="/assets/documentation.html-B-SWCtsa.js" as="script"><link rel="prefetch" href="/assets/testing.html-DIbeaaFd.js" as="script"><link rel="prefetch" href="/assets/index.html-D1cGf28t.js" as="script"><link rel="prefetch" href="/assets/graphql.html-hAUzkKsr.js" as="script"><link rel="prefetch" href="/assets/neo4j-graphql-js.html-CoZVYA65.js" as="script"><link rel="prefetch" href="/assets/testing.html-BI46pcUx.js" as="script"><link rel="prefetch" href="/assets/index.html-vLTkaCiA.js" as="script"><link rel="prefetch" href="/assets/features.html-jBF7v5Tb.js" as="script"><link rel="prefetch" href="/assets/TODO-next-update.html-Dbd1HQYH.js" as="script"><link rel="prefetch" href="/assets/deployment-values.html-BuK-P7uz.js" as="script"><link rel="prefetch" href="/assets/index.html-DA6JIG7N.js" as="script"><link rel="prefetch" href="/assets/index.html-DTeYXEMn.js" as="script"><link rel="prefetch" href="/assets/index.html-BotCCvmf.js" as="script"><link rel="prefetch" href="/assets/components.html-eb5EQ5pR.js" as="script"><link rel="prefetch" href="/assets/html.html-D297kDdJ.js" as="script"><link rel="prefetch" href="/assets/scss.html-B5_5w6uH.js" as="script"><link rel="prefetch" href="/assets/testing.html-B-yKt6km.js" as="script"><link rel="prefetch" href="/assets/vue.html-usvwTHmn.js" as="script"><link rel="prefetch" href="/assets/index.html-e225B3G6.js" as="script"><link rel="prefetch" href="/assets/CONTRIBUTING.html-vtvi86EK.js" as="script"><link rel="prefetch" href="/assets/KATALOG.html-mRGwYwFN.js" as="script"><link rel="prefetch" href="/assets/index.html-CvqC5bIY.js" as="script"><link rel="prefetch" href="/assets/index.html-CMfno-Jk.js" as="script"><link rel="prefetch" href="/assets/GraphQL-Playground.html-KHcgqCNs.js" as="script"><link rel="prefetch" href="/assets/index.html-Cei9bcC1.js" as="script"><link rel="prefetch" href="/assets/index.html-kBh9kVNW.js" as="script"><link rel="prefetch" href="/assets/demo.html-BiUsynyZ.js" as="script"><link rel="prefetch" href="/assets/DataDisplay.html-Sq6mPmsX.js" as="script"><link rel="prefetch" href="/assets/DesignTokens.html-BMTgeCjs.js" as="script"><link rel="prefetch" href="/assets/Introduction.html-BLSaoncO.js" as="script"><link rel="prefetch" href="/assets/Layout.html-47srRE8z.js" as="script"><link rel="prefetch" href="/assets/Navigation.html-CYz7rQzP.js" as="script"><link rel="prefetch" href="/assets/Typography.html-DOSESWuO.js" as="script"><link rel="prefetch" href="/assets/STATUS.html-BXjDgreh.js" as="script"><link rel="prefetch" href="/assets/index.html-Cww0Dhaz.js" as="script"><link rel="prefetch" href="/assets/demo.html-B0pnlV6F.js" as="script"><link rel="prefetch" href="/assets/demo.html-CW3cD-H7.js" as="script"><link rel="prefetch" href="/assets/demo.html-6dfjrebI.js" as="script"><link rel="prefetch" href="/assets/demo.html-C6Qxo9Yj.js" as="script"><link rel="prefetch" href="/assets/demo.html-Cry2GElr.js" as="script"><link rel="prefetch" href="/assets/demo.html-D5WfjYVc.js" as="script"><link rel="prefetch" href="/assets/demo.html-DgTy1wh7.js" as="script"><link rel="prefetch" href="/assets/demo.html-BJe7I_KV.js" as="script"><link rel="prefetch" href="/assets/demo.html-4tqTFmIl.js" as="script"><link rel="prefetch" href="/assets/demo.html-DYuC5F42.js" as="script"><link rel="prefetch" href="/assets/demo.html-yVAERFv_.js" as="script"><link rel="prefetch" href="/assets/demo.html-BvyZLl5C.js" as="script"><link rel="prefetch" href="/assets/demo.html-BycQma2M.js" as="script"><link rel="prefetch" href="/assets/demo.html-DtB7qtVV.js" as="script"><link rel="prefetch" href="/assets/demo.html-DzDfUj7H.js" as="script"><link rel="prefetch" href="/assets/demo.html-C19sXN7o.js" as="script"><link rel="prefetch" href="/assets/demo.html-Kr4R8KpK.js" as="script"><link rel="prefetch" href="/assets/demo.html-BF4J6jMP.js" as="script"><link rel="prefetch" href="/assets/demo.html-BI-OLisc.js" as="script"><link rel="prefetch" href="/assets/demo.html-B9TFHr4o.js" as="script"><link rel="prefetch" href="/assets/demo.html-CWpO2OIy.js" as="script"><link rel="prefetch" href="/assets/demo.html-pp2XB-6I.js" as="script"><link rel="prefetch" href="/assets/demo.html-ESNHpVvJ.js" as="script"><link rel="prefetch" href="/assets/demo.html-CREqiFsx.js" as="script"><link rel="prefetch" href="/assets/demo.html-DiSwsATb.js" as="script"><link rel="prefetch" href="/assets/demo.html-7n0yJEH_.js" as="script"><link rel="prefetch" href="/assets/demo.html-B_2DIOaF.js" as="script"><link rel="prefetch" href="/assets/demo.html-DWLc6gFO.js" as="script"><link rel="prefetch" href="/assets/demo.html-DmAuEWx0.js" as="script"><link rel="prefetch" href="/assets/404.html-CRHdR_pJ.js" as="script"><link rel="prefetch" href="/assets/index.html-Buh1ncoF.js" as="script"><link rel="prefetch" href="/assets/index.html-DGSGkBMu.js" as="script"><link rel="prefetch" href="/assets/index.html-JQc0YsYs.js" as="script"><link rel="prefetch" href="/assets/index.html-TA2-vxe5.js" as="script"><link rel="prefetch" href="/assets/index.html-trrnbNUq.js" as="script"><link rel="prefetch" href="/assets/index.html-cfpWt2jJ.js" as="script"><link rel="prefetch" href="/assets/index.html-D6JUTu4R.js" as="script"><link rel="prefetch" href="/assets/index.html-Bo7Ljf3a.js" as="script"><link rel="prefetch" href="/assets/index.html-Ch-l52j_.js" as="script"><link rel="prefetch" href="/assets/index.html-IpTOngCN.js" as="script"><link rel="prefetch" href="/assets/index.html-LVrUA5Ul.js" as="script"><link rel="prefetch" href="/assets/index.html-NfLl_DKG.js" as="script"><link rel="prefetch" href="/assets/index.html-DVXBW6uR.js" as="script"><link rel="prefetch" href="/assets/index.html-B9A0hIU3.js" as="script"><link rel="prefetch" href="/assets/index.html-DzJKTok5.js" as="script"><link rel="prefetch" href="/assets/index.html-Bnl9T6N5.js" as="script"><link rel="prefetch" href="/assets/index.html-CT0wGi5f.js" as="script"><link rel="prefetch" href="/assets/index.html-DwrHoA30.js" as="script"><link rel="prefetch" href="/assets/index.html-BSwbVjcn.js" as="script"><link rel="prefetch" href="/assets/index.html-BtnavBbg.js" as="script"><link rel="prefetch" href="/assets/index.html-D3mIxgfr.js" as="script"><link rel="prefetch" href="/assets/index.html-BrgOXTUr.js" as="script"><link rel="prefetch" href="/assets/index.html-CKtpFMus.js" as="script"><link rel="prefetch" href="/assets/index.html-BSOluCGA.js" as="script"><link rel="prefetch" href="/assets/index.html-CrpjDBoX.js" as="script"><link rel="prefetch" href="/assets/index.html-CZTXNS31.js" as="script"><link rel="prefetch" href="/assets/index.html-Bm1PPhY4.js" as="script"><link rel="prefetch" href="/assets/index.html-CI2ipqR1.js" as="script"><link rel="prefetch" href="/assets/index.html-Bbb8AMlW.js" as="script"><link rel="prefetch" href="/assets/index.html-CsIJCUeh.js" as="script"><link rel="prefetch" href="/assets/index.html-B2TWVP9L.js" as="script"><link rel="prefetch" href="/assets/index.html-Bwei2ul_.js" as="script"><link rel="prefetch" href="/assets/index.html-BsZpCI8J.js" as="script"><link rel="prefetch" href="/assets/index.html-BrAtRvv1.js" as="script"><link rel="prefetch" href="/assets/index.html-CF1IpUUb.js" as="script"><link rel="prefetch" href="/assets/index.html-DfhQXQ0d.js" as="script"><link rel="prefetch" href="/assets/index.html-CMM88C29.js" as="script"><link rel="prefetch" href="/assets/index.html-DXR7MLPg.js" as="script"><link rel="prefetch" href="/assets/index.html-VtoA8tN6.js" as="script"><link rel="prefetch" href="/assets/index.html-DnBsIglD.js" as="script"><link rel="prefetch" href="/assets/index.html-Ca3OZ9Wf.js" as="script"><link rel="prefetch" href="/assets/index.html-BEQOMUJ-.js" as="script"><link rel="prefetch" href="/assets/index.html-2oMIHQ-B.js" as="script"><link rel="prefetch" href="/assets/index.html-Bv1cv5QZ.js" as="script"><link rel="prefetch" href="/assets/index.html-TZE5wDuj.js" as="script"><link rel="prefetch" href="/assets/index.html-BOfzPdYe.js" as="script"><link rel="prefetch" href="/assets/index.html-B7ltYIGI.js" as="script"><link rel="prefetch" href="/assets/index.html-Cc8SuVO9.js" as="script"><link rel="prefetch" href="/assets/index.html-TIaxxz0H.js" as="script"><link rel="prefetch" href="/assets/index.html-DlRSH8xw.js" as="script"><link rel="prefetch" href="/assets/index.html-BC6ChnxU.js" as="script"><link rel="prefetch" href="/assets/photoswipe.esm-GXRgw7eJ.js" as="script"><link rel="prefetch" href="/assets/SearchResult-CRxy2nzi.js" as="script"><link rel="prefetch" href="/assets/setupDevtools-7MC2TMWH-B7E_wV2L.js" as="script">
|
||
</head>
|
||
<body>
|
||
<div id="app"><!--[--><!--[--><!--[--><span tabindex="-1"></span><a href="#main-content" class="vp-skip-link sr-only">Skip to main content</a><!--]--><!--[--><div class="theme-container external-link-icon pure has-toc" vp-container><!--[--><header id="navbar" class="vp-navbar" vp-navbar><div class="vp-navbar-start"><button type="button" class="vp-toggle-sidebar-button" title="Toggle Sidebar"><span class="icon"></span></button><!----><!--[--><a class="route-link vp-brand" href="/" aria-label="Take me home"><img class="vp-nav-logo" src="/logo.svg" alt><!----><span class="vp-site-name hide-in-pad">Ocelot.Social Documentation</span></a><!--]--><!----></div><div class="vp-navbar-center"><!----><!--[--><nav class="vp-nav-links"><div class="vp-nav-item hide-in-mobile"><a class="auto-link external-link" href="https://stage.ocelot.social/" aria-label="Demo" target="_self"><!---->Demo<!----></a></div></nav><!--]--><!----></div><div class="vp-navbar-end"><!----><!--[--><div class="vp-nav-item vp-action"><a class="vp-action-link" href="https://github.com/Ocelot-Social-Community/Ocelot-Social" target="_blank" rel="noopener noreferrer" aria-label="GitHub"><svg xmlns="http://www.w3.org/2000/svg" class="icon github-icon" viewBox="0 0 1024 1024" fill="currentColor" aria-label="github icon" name="github" style="width:1.25rem;height:1.25rem;vertical-align:middle;"><path d="M511.957 21.333C241.024 21.333 21.333 240.981 21.333 512c0 216.832 140.544 400.725 335.574 465.664 24.49 4.395 32.256-10.07 32.256-23.083 0-11.69.256-44.245 0-85.205-136.448 29.61-164.736-64.64-164.736-64.64-22.315-56.704-54.4-71.765-54.4-71.765-44.587-30.464 3.285-29.824 3.285-29.824 49.195 3.413 75.179 50.517 75.179 50.517 43.776 75.008 114.816 53.333 142.762 40.79 4.523-31.66 17.152-53.377 31.19-65.537-108.971-12.458-223.488-54.485-223.488-242.602 0-53.547 19.114-97.323 50.517-131.67-5.035-12.33-21.93-62.293 4.779-129.834 0 0 41.258-13.184 134.912 50.346a469.803 469.803 0 0 1 122.88-16.554c41.642.213 83.626 5.632 122.88 16.554 93.653-63.488 134.784-50.346 134.784-50.346 26.752 67.541 9.898 117.504 4.864 129.834 31.402 34.347 50.474 78.123 50.474 131.67 0 188.586-114.73 230.016-224.042 242.09 17.578 15.232 33.578 44.672 33.578 90.454v135.85c0 13.142 7.936 27.606 32.854 22.87C862.25 912.597 1002.667 728.747 1002.667 512c0-271.019-219.648-490.667-490.71-490.667z"></path></svg></a></div><div class="vp-nav-item hide-in-mobile"><button type="button" class="vp-color-mode-switch" id="color-mode-switch"><svg xmlns="http://www.w3.org/2000/svg" class="icon auto-icon" viewBox="0 0 1024 1024" fill="currentColor" aria-label="auto icon" name="auto" style="display:block;"><path d="M512 992C246.92 992 32 777.08 32 512S246.92 32 512 32s480 214.92 480 480-214.92 480-480 480zm0-840c-198.78 0-360 161.22-360 360 0 198.84 161.22 360 360 360s360-161.16 360-360c0-198.78-161.22-360-360-360zm0 660V212c165.72 0 300 134.34 300 300 0 165.72-134.28 300-300 300z"></path></svg><svg xmlns="http://www.w3.org/2000/svg" class="icon dark-icon" viewBox="0 0 1024 1024" fill="currentColor" aria-label="dark icon" name="dark" style="display:none;"><path d="M524.8 938.667h-4.267a439.893 439.893 0 0 1-313.173-134.4 446.293 446.293 0 0 1-11.093-597.334A432.213 432.213 0 0 1 366.933 90.027a42.667 42.667 0 0 1 45.227 9.386 42.667 42.667 0 0 1 10.24 42.667 358.4 358.4 0 0 0 82.773 375.893 361.387 361.387 0 0 0 376.747 82.774 42.667 42.667 0 0 1 54.187 55.04 433.493 433.493 0 0 1-99.84 154.88 438.613 438.613 0 0 1-311.467 128z"></path></svg><svg xmlns="http://www.w3.org/2000/svg" class="icon light-icon" viewBox="0 0 1024 1024" fill="currentColor" aria-label="light icon" name="light" style="display:none;"><path d="M952 552h-80a40 40 0 0 1 0-80h80a40 40 0 0 1 0 80zM801.88 280.08a41 41 0 0 1-57.96-57.96l57.96-58a41.04 41.04 0 0 1 58 58l-58 57.96zM512 752a240 240 0 1 1 0-480 240 240 0 0 1 0 480zm0-560a40 40 0 0 1-40-40V72a40 40 0 0 1 80 0v80a40 40 0 0 1-40 40zm-289.88 88.08-58-57.96a41.04 41.04 0 0 1 58-58l57.96 58a41 41 0 0 1-57.96 57.96zM192 512a40 40 0 0 1-40 40H72a40 40 0 0 1 0-80h80a40 40 0 0 1 40 40zm30.12 231.92a41 41 0 0 1 57.96 57.96l-57.96 58a41.04 41.04 0 0 1-58-58l58-57.96zM512 832a40 40 0 0 1 40 40v80a40 40 0 0 1-80 0v-80a40 40 0 0 1 40-40zm289.88-88.08 58 57.96a41.04 41.04 0 0 1-58 58l-57.96-58a41 41 0 0 1 57.96-57.96z"></path></svg></button></div><!--[--><button type="button" class="search-pro-button" aria-label="Search"><svg xmlns="http://www.w3.org/2000/svg" class="icon search-icon" viewBox="0 0 1024 1024" fill="currentColor" aria-label="search icon" name="search"><path d="M192 480a256 256 0 1 1 512 0 256 256 0 0 1-512 0m631.776 362.496-143.2-143.168A318.464 318.464 0 0 0 768 480c0-176.736-143.264-320-320-320S128 303.264 128 480s143.264 320 320 320a318.016 318.016 0 0 0 184.16-58.592l146.336 146.368c12.512 12.48 32.768 12.48 45.28 0 12.48-12.512 12.48-32.768 0-45.28"></path></svg><div class="search-pro-placeholder">Search</div><div class="search-pro-key-hints"><kbd class="search-pro-key">Ctrl</kbd><kbd class="search-pro-key">K</kbd></div></button><!--]--><!--]--><!----><button type="button" class="vp-toggle-navbar-button" aria-label="Toggle Navbar" aria-expanded="false" aria-controls="nav-screen"><span><span class="vp-top"></span><span class="vp-middle"></span><span class="vp-bottom"></span></span></button></div></header><!----><!--]--><!----><div class="toggle-sidebar-wrapper"><span class="arrow start"></span></div><aside id="sidebar" class="vp-sidebar" vp-sidebar><!----><ul class="vp-sidebar-links"><li><a class="route-link auto-link vp-sidebar-link" href="/" aria-label="Introduction"><!---->Introduction<!----></a></li><li><a class="route-link auto-link vp-sidebar-link" href="/neo4j/" aria-label="Neo4J"><!---->Neo4J<!----></a></li><li><section class="vp-sidebar-group"><p class="vp-sidebar-header clickable"><!----><a class="route-link auto-link vp-sidebar-title no-external-link-icon" href="/backend/" aria-label="Backend"><!---->Backend<!----></a><!----></p><ul class="vp-sidebar-links"><li><a class="route-link auto-link vp-sidebar-link" href="/backend/graphql.html" aria-label="GraphQL"><!---->GraphQL<!----></a></li><li><a class="route-link auto-link vp-sidebar-link" href="/backend/neo4j-graphql-js.html" aria-label="neo4j-graphql-js"><!---->neo4j-graphql-js<!----></a></li></ul></section></li><li><section class="vp-sidebar-group"><p class="vp-sidebar-header clickable"><!----><a class="route-link auto-link vp-sidebar-title no-external-link-icon" href="/webapp/" aria-label="Webapp (Frontend)"><!---->Webapp (Frontend)<!----></a><!----></p><ul class="vp-sidebar-links"><li><a class="route-link auto-link vp-sidebar-link" href="/webapp/components.html" aria-label="Components"><!---->Components<!----></a></li><li><a class="route-link auto-link vp-sidebar-link" href="/webapp/html.html" aria-label="HTML"><!---->HTML<!----></a></li><li><a class="route-link auto-link vp-sidebar-link" href="/webapp/scss.html" aria-label="SCSS"><!---->SCSS<!----></a></li><li><a class="route-link auto-link vp-sidebar-link" href="/webapp/vue.html" aria-label="Vue"><!---->Vue<!----></a></li></ul></section></li><li><section class="vp-sidebar-group"><p class="vp-sidebar-header clickable"><!----><a class="route-link auto-link vp-sidebar-title no-external-link-icon" href="/testing.html" aria-label="Testing Guide"><!---->Testing Guide<!----></a><!----></p><ul class="vp-sidebar-links"><li><a class="route-link auto-link vp-sidebar-link" href="/cypress/" aria-label="End-to-end Tests"><!---->End-to-end Tests<!----></a></li><li><a class="route-link auto-link vp-sidebar-link" href="/webapp/testing.html" aria-label="Webapp (Frontend) Tests"><!---->Webapp (Frontend) Tests<!----></a></li><li><a class="route-link auto-link vp-sidebar-link" href="/backend/testing.html" aria-label="Backend Tests"><!---->Backend Tests<!----></a></li></ul></section></li><li><a class="route-link auto-link vp-sidebar-link" href="/deployment/" aria-label="Deployment"><!---->Deployment<!----></a></li><li><a class="route-link auto-link vp-sidebar-link" href="/CONTRIBUTING.html" aria-label="Contributing"><!---->Contributing<!----></a></li><li><a class="route-link auto-link vp-sidebar-link" href="/cypress/features.html" aria-label="Feature Specification"><!---->Feature Specification<!----></a></li><li><a class="route-link auto-link vp-sidebar-link" href="/CODE_OF_CONDUCT.html" aria-label="Code of Conduct"><!---->Code of Conduct<!----></a></li><li><a class="route-link auto-link vp-sidebar-link" href="/documentation.html" aria-label="Documentation"><!---->Documentation<!----></a></li><li><a class="route-link auto-link vp-sidebar-link" href="/LICENSE.html" aria-label="License"><!---->License<!----></a></li></ul><!----></aside><!--[--><!--[--><main id="main-content" class="vp-page"><!--[--><!----><!----><nav class="vp-breadcrumb disable"></nav><div class="vp-page-title"><h1><!---->@ocelot-social/ui - Projektdokumentation</h1><div class="page-info"><!----><!----><span class="page-date-info" aria-label="Writing Date"><svg xmlns="http://www.w3.org/2000/svg" class="icon calendar-icon" viewBox="0 0 1024 1024" fill="currentColor" aria-label="calendar icon" name="calendar"><path d="M716.4 110.137c0-18.753-14.72-33.473-33.472-33.473-18.753 0-33.473 14.72-33.473 33.473v33.473h66.993v-33.473zm-334.87 0c0-18.753-14.72-33.473-33.473-33.473s-33.52 14.72-33.52 33.473v33.473h66.993v-33.473zm468.81 33.52H716.4v100.465c0 18.753-14.72 33.473-33.472 33.473a33.145 33.145 0 01-33.473-33.473V143.657H381.53v100.465c0 18.753-14.72 33.473-33.473 33.473a33.145 33.145 0 01-33.473-33.473V143.657H180.6A134.314 134.314 0 0046.66 277.595v535.756A134.314 134.314 0 00180.6 947.289h669.74a134.36 134.36 0 00133.94-133.938V277.595a134.314 134.314 0 00-133.94-133.938zm33.473 267.877H147.126a33.145 33.145 0 01-33.473-33.473c0-18.752 14.72-33.473 33.473-33.473h736.687c18.752 0 33.472 14.72 33.472 33.473a33.145 33.145 0 01-33.472 33.473z"></path></svg><span data-allow-mismatch="text">March 1, 2026</span><meta property="datePublished" content="2026-03-01T11:39:30.000Z"></span><!----><span class="page-reading-time-info" aria-label="Reading Time"><svg xmlns="http://www.w3.org/2000/svg" class="icon timer-icon" viewBox="0 0 1024 1024" fill="currentColor" aria-label="timer icon" name="timer"><path d="M799.387 122.15c4.402-2.978 7.38-7.897 7.38-13.463v-1.165c0-8.933-7.38-16.312-16.312-16.312H256.33c-8.933 0-16.311 7.38-16.311 16.312v1.165c0 5.825 2.977 10.874 7.637 13.592 4.143 194.44 97.22 354.963 220.201 392.763-122.204 37.542-214.893 196.511-220.2 389.397-4.661 5.049-7.638 11.651-7.638 19.03v5.825h566.49v-5.825c0-7.379-2.849-13.981-7.509-18.9-5.049-193.016-97.867-351.985-220.2-389.527 123.24-37.67 216.446-198.453 220.588-392.892zM531.16 450.445v352.632c117.674 1.553 211.787 40.778 211.787 88.676H304.097c0-48.286 95.149-87.382 213.728-88.676V450.445c-93.077-3.107-167.901-81.297-167.901-177.093 0-8.803 6.99-15.793 15.793-15.793 8.803 0 15.794 6.99 15.794 15.793 0 80.261 63.69 145.635 142.01 145.635s142.011-65.374 142.011-145.635c0-8.803 6.99-15.793 15.794-15.793s15.793 6.99 15.793 15.793c0 95.019-73.789 172.82-165.96 177.093z"></path></svg><span>About 53 min</span><meta property="timeRequired" content="PT53M"></span><!----><!----></div><hr></div><div class="vp-toc-placeholder"><aside id="toc" vp-toc><!----><div class="vp-toc-header">On This Page<!----><div class="arrow end"></div></div><div class="vp-toc-wrapper"><ul class="vp-toc-list"><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level2" href="#inhaltsverzeichnis">Inhaltsverzeichnis</a></li><li><ul class="vp-toc-list"><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#schnellzugriff-status">Schnellzugriff (Status)</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#nach-thema">Nach Thema</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#wie-dieses-dokument-verwendet-wird">Wie dieses Dokument verwendet wird</a></li><!----><!--]--></ul></li><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level2" href="#fortschritt">Fortschritt</a></li><li><ul class="vp-toc-list"><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#gesamtprojekt">Gesamtprojekt</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#katalogisierung-details-in-katalog-md">Katalogisierung (Details in KATALOG.md)</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#osbutton-migration-phase-3-✅">OsButton Migration (Phase 3) ✅</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#osicon-phase-4">OsIcon (Phase 4)</a></li><!----><!--]--></ul></li><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level2" href="#aktueller-stand">Aktueller Stand</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level2" href="#meilensteine">Meilensteine</a></li><li><ul class="vp-toc-list"><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#phase-0-analyse-katalogisierung-✅">Phase 0: Analyse & Katalogisierung ✅</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#phase-1-vue-2-7-upgrade-✅">Phase 1: Vue 2.7 Upgrade ✅</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#phase-2-projekt-setup-✅">Phase 2: Projekt-Setup ✅</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#phase-3-webapp-integration-validierung">Phase 3: Webapp-Integration (Validierung)</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#phase-4-komponenten-migration">Phase 4: Komponenten-Migration</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#phase-5-finalisierung">Phase 5: Finalisierung</a></li><!----><!--]--></ul></li><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level2" href="#_1-projektziel-vision">1. Projektziel & Vision</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level2" href="#_2-tech-stack">2. Tech-Stack</a></li><li><ul class="vp-toc-list"><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#konventionen">Konventionen</a></li><!----><!--]--></ul></li><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level2" href="#_3-build-distribution">3. Build & Distribution</a></li><li><ul class="vp-toc-list"><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#dual-build-strategie">Dual-Build Strategie</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#nutzung-mit-tailwind">Nutzung MIT Tailwind</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#nutzung-ohne-tailwind">Nutzung OHNE Tailwind</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#branding-funktioniert-fur-beide">Branding (funktioniert für beide)</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#webapp-integration-entwicklung">Webapp-Integration (Entwicklung)</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#webapp-aufgaben-todo-fur-ocelot-webapp">Webapp-Aufgaben (TODO für Ocelot-Webapp)</a></li><!----><!--]--></ul></li><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level2" href="#_4-icon-architektur">4. Icon-Architektur</a></li><li><ul class="vp-toc-list"><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#entscheidung-hybrid-ansatz">Entscheidung: Hybrid-Ansatz</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#begrundung">Begründung</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#system-icons-in-library-enthalten-✅">System-Icons (in Library enthalten) ✅</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#api-design-implementiert">API-Design (implementiert)</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#svg-loading-vite-svg-icon-plugin">SVG-Loading (vite-svg-icon Plugin)</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#ocelot-icons-separates-entry-point">Ocelot-Icons (separates Entry-Point)</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#aktuelle-icon-statistik">Aktuelle Icon-Statistik</a></li><!----><!--]--></ul></li><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level2" href="#_5-design-token-branding">5. Design-Token & Branding</a></li><li><ul class="vp-toc-list"><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#_3-stufen-token-system">3-Stufen Token-System</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#branding-flexibilitat">Branding-Flexibilität</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#kompatibilitat-mit-bestehendem-system">Kompatibilität mit bestehendem System</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#cva-tailwind-css-variablen">CVA + Tailwind + CSS-Variablen</a></li><!----><!--]--></ul></li><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level2" href="#_6-ci-cd-release">6. CI/CD & Release</a></li><li><ul class="vp-toc-list"><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#release-workflow-release-please">Release-Workflow (release-please)</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#github-workflows">GitHub Workflows</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#qualitatssicherung-bei-prs">Qualitätssicherung bei PRs</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#automatische-deployments">Automatische Deployments</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#github-workflows-vollstandige-liste">GitHub Workflows (vollständige Liste)</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#erweiterte-qualitatssicherung">Erweiterte Qualitätssicherung</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#migrations-absicherung">Migrations-Absicherung</a></li><!----><!--]--></ul></li><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level2" href="#_7-dokumentation-dx">7. Dokumentation & DX</a></li><li><ul class="vp-toc-list"><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#storybook-als-komponenten-dokumentation">Storybook als Komponenten-Dokumentation</a></li><!----><!--]--></ul></li><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level2" href="#_8-migrationsstrategie">8. Migrationsstrategie</a></li><li><ul class="vp-toc-list"><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#grundprinzipien">Grundprinzipien</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#ablauf-pro-komponente">Ablauf pro Komponente</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#komponenten-protokoll">Komponenten-Protokoll</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#qualitatsanforderungen-pro-komponente">Qualitätsanforderungen pro Komponente</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#konsolidierungsziele">Konsolidierungsziele</a></li><!----><!--]--></ul></li><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level2" href="#_9-dokumentationsstrategie">9. Dokumentationsstrategie</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level2" href="#_10-kompatibilitatstests">10. Kompatibilitätstests</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level2" href="#_11-entscheidungen">11. Entscheidungen</a></li><li><ul class="vp-toc-list"><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#vision-ziele">Vision & Ziele</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#tech-stack">Tech-Stack</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#build-distribution">Build & Distribution</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#ci-cd-release">CI/CD & Release</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#testing-qualitat">Testing & Qualität</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#design-system-branding">Design-System & Branding</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#komponenten-api-konventionen">Komponenten-API & Konventionen</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#dokumentation">Dokumentation</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#migration-prozess">Migration & Prozess</a></li><!----><!--]--></ul></li><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level2" href="#_12-arbeitsprotokoll">12. Arbeitsprotokoll</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level2" href="#_13-komponenten-katalog">13. Komponenten-Katalog</a></li><li><ul class="vp-toc-list"><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#zusammenfassung-aus-katalog-md">Zusammenfassung (aus KATALOG.md)</a></li><!----><!--]--></ul></li><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level2" href="#_14-ressourcen-links">14. Ressourcen & Links</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level2" href="#_15-dokumentationsstrategie-details">15. Dokumentationsstrategie (Details)</a></li><li><ul class="vp-toc-list"><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#ubersicht">Übersicht</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#readme-md-struktur">README.md Struktur</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#generierung-mit-vue-component-meta">Generierung mit vue-component-meta</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#ci-workflow-docs-check">CI-Workflow: docs-check</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#eslint-regeln-fur-dokumentation">ESLint-Regeln für Dokumentation</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#dokumentations-phasen">Dokumentations-Phasen</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#nach-der-migration-datei-transformation">Nach der Migration: Datei-Transformation</a></li><!----><!--]--></ul></li><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level2" href="#_16-library-vs-webapp">16. Library vs. Webapp</a></li><li><ul class="vp-toc-list"><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#grundprinzip">Grundprinzip</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#entscheidungs-checkliste">Entscheidungs-Checkliste</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#entscheidungsbaum">Entscheidungsbaum</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#quantitative-regel">Quantitative Regel</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#konkrete-beispiele">Konkrete Beispiele</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#composition-pattern-fur-grenzfalle">Composition Pattern für Grenzfälle</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#checkliste-bei-neuer-komponente">Checkliste bei neuer Komponente</a></li><!----><!--]--></ul></li><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level2" href="#_16a-webapp-↔-maintenance-code-sharing">16a. Webapp ↔ Maintenance Code-Sharing</a></li><li><ul class="vp-toc-list"><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#problemstellung">Problemstellung</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#analysierte-optionen">Analysierte Optionen</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#empfehlung-option-c-webapp-als-source-of-truth">Empfehlung: Option C (Webapp als Source of Truth)</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#umsetzung">Umsetzung</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#kriterien-fur-entwickler">Kriterien für Entwickler</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#vorteile">Vorteile</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#spatere-evolution-optional">Spätere Evolution (optional)</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#entscheidung">Entscheidung</a></li><!----><!--]--></ul></li><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level2" href="#_16b-daten-entkopplung-viewmodel-mapper-pattern">16b. Daten-Entkopplung (ViewModel/Mapper Pattern)</a></li><li><ul class="vp-toc-list"><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#problemstellung-1">Problemstellung</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#losung-viewmodel-mapper-pattern">Lösung: ViewModel + Mapper Pattern</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#implementierung">Implementierung</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#ordnerstruktur">Ordnerstruktur</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#vorteile-1">Vorteile</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#regeln">Regeln</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#entscheidung-1">Entscheidung</a></li><!----><!--]--></ul></li><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level2" href="#_17-externe-abhangigkeiten">17. Externe Abhängigkeiten</a></li><li><ul class="vp-toc-list"><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#ubersicht-1">Übersicht</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#eslint-config-it4c">eslint-config-it4c</a></li><!----><!--]--></ul></li><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level2" href="#_18-kompatibilitatstests-details">18. Kompatibilitätstests (Details)</a></li><li><ul class="vp-toc-list"><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#testmatrix">Testmatrix</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#werkzeuge-strategien">Werkzeuge & Strategien</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#ci-workflow-compatibility-matrix">CI Workflow: Compatibility Matrix</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#werkzeug-ubersicht">Werkzeug-Übersicht</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#checkliste-fur-neue-komponenten">Checkliste für neue Komponenten</a></li><!----><!--]--></ul></li><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level2" href="#_19-komplexitatsanalyse">19. Komplexitätsanalyse</a></li><li><ul class="vp-toc-list"><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#umfang-nach-phasen">Umfang nach Phasen</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#bekannte-risikofaktoren">Bekannte Risikofaktoren</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#parallelisierbarkeit">Parallelisierbarkeit</a></li><!----><!--]--><!--[--><li class="vp-toc-item"><a class="route-link vp-toc-link level3" href="#aufwandstreiber-pro-komponente-phase-4">Aufwandstreiber pro Komponente (Phase 4)</a></li><!----><!--]--></ul></li><!--]--></ul><div class="vp-toc-marker" style="top:-1.7rem;"></div></div><!----></aside></div><!----><div class="theme-hope-content" vp-content><h1 id="ocelot-social-ui-projektdokumentation" tabindex="-1"><a class="header-anchor" href="#ocelot-social-ui-projektdokumentation"><span>@ocelot-social/ui - Projektdokumentation</span></a></h1><blockquote><p>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.</p></blockquote><hr><h2 id="inhaltsverzeichnis" tabindex="-1"><a class="header-anchor" href="#inhaltsverzeichnis"><span>Inhaltsverzeichnis</span></a></h2><h3 id="schnellzugriff-status" tabindex="-1"><a class="header-anchor" href="#schnellzugriff-status"><span>Schnellzugriff (Status)</span></a></h3><table><thead><tr><th>Abschnitt</th><th>Beschreibung</th></tr></thead><tbody><tr><td><a href="#fortschritt">Fortschritt</a></td><td>Visuelle Fortschrittsanzeige</td></tr><tr><td><a href="#aktueller-stand">Aktueller Stand</a></td><td>Was zuletzt erledigt wurde</td></tr><tr><td><a href="#meilensteine">Meilensteine</a></td><td>Phasen 0-5 mit Checklisten</td></tr></tbody></table><h3 id="nach-thema" tabindex="-1"><a class="header-anchor" href="#nach-thema"><span>Nach Thema</span></a></h3><p><strong><a href="#vision">VISION</a></strong></p><table><thead><tr><th>#</th><th>Abschnitt</th></tr></thead><tbody><tr><td>1</td><td><a href="#1-projektziel--vision">Projektziel & Vision</a></td></tr></tbody></table><p><strong><a href="#technische-grundlagen">TECHNISCHE GRUNDLAGEN</a></strong></p><table><thead><tr><th>#</th><th>Abschnitt</th></tr></thead><tbody><tr><td>2</td><td><a href="#2-tech-stack">Tech-Stack</a></td></tr><tr><td>3</td><td><a href="#3-build--distribution">Build & Distribution</a></td></tr><tr><td>4</td><td><a href="#4-icon-architektur">Icon-Architektur</a></td></tr><tr><td>5</td><td><a href="#5-design-token--branding">Design-Token & Branding</a></td></tr></tbody></table><p><strong><a href="#prozesse--qualit%C3%A4t">PROZESSE & QUALITÄT</a></strong></p><table><thead><tr><th>#</th><th>Abschnitt</th></tr></thead><tbody><tr><td>6</td><td><a href="#6-cicd--release">CI/CD & Release</a></td></tr><tr><td>7</td><td><a href="#7-dokumentation--dx">Dokumentation & DX</a></td></tr><tr><td>8</td><td><a href="#8-migrationsstrategie">Migrationsstrategie</a></td></tr><tr><td>9</td><td><a href="#9-dokumentationsstrategie">Dokumentationsstrategie</a></td></tr><tr><td>10</td><td><a href="#10-kompatibilit%C3%A4tstests">Kompatibilitätstests</a></td></tr></tbody></table><p><strong><a href="#referenz--historie">REFERENZ & HISTORIE</a></strong></p><table><thead><tr><th>#</th><th>Abschnitt</th></tr></thead><tbody><tr><td>11</td><td><a href="#11-entscheidungen">Entscheidungen</a></td></tr><tr><td>12</td><td><a href="#12-arbeitsprotokoll">Arbeitsprotokoll</a></td></tr><tr><td>13</td><td><a href="#13-komponenten-katalog">Komponenten-Katalog</a></td></tr><tr><td>14</td><td><a href="#14-ressourcen--links">Ressourcen & Links</a></td></tr><tr><td>15</td><td><a href="#15-dokumentationsstrategie-details">Dokumentationsstrategie (Details)</a></td></tr></tbody></table><p><strong><a href="#abgrenzungen">ABGRENZUNGEN</a></strong></p><table><thead><tr><th>#</th><th>Abschnitt</th></tr></thead><tbody><tr><td>16</td><td><a href="#16-library-vs-webapp">Library vs. Webapp</a></td></tr><tr><td>16a</td><td><a href="#16a-webapp--maintenance-code-sharing">Webapp ↔ Maintenance Code-Sharing</a></td></tr><tr><td>16b</td><td><a href="#16b-daten-entkopplung-viewmodelmapper-pattern">Daten-Entkopplung (ViewModel/Mapper)</a></td></tr><tr><td>17</td><td><a href="#17-externe-abh%C3%A4ngigkeiten">Externe Abhängigkeiten</a></td></tr><tr><td>18</td><td><a href="#18-kompatibilit%C3%A4tstests-details">Kompatibilitätstests (Details)</a></td></tr><tr><td>19</td><td><a href="#19-komplexit%C3%A4tsanalyse">Komplexitätsanalyse</a></td></tr></tbody></table><hr><h3 id="wie-dieses-dokument-verwendet-wird" tabindex="-1"><a class="header-anchor" href="#wie-dieses-dokument-verwendet-wird"><span>Wie dieses Dokument verwendet wird</span></a></h3><p><strong>Zum Fortsetzen der Arbeit:</strong></p><blockquote><p>"Lass uns am @ocelot-social/ui Projekt weiterarbeiten" (packages/ui)</p></blockquote><p><strong>Nach jeder Session aktualisieren:</strong></p><ul><li>"Fortschritt" – Balkendiagramme aktualisieren</li><li>"Aktueller Stand" – Zuletzt erledigte Aufgaben</li><li>"Meilensteine" – Checklisten abhaken</li><li>§12 "Arbeitsprotokoll" – Neue Einträge hinzufügen</li><li><code>KATALOG.md</code> – Komponenten-Status pflegen</li></ul><hr><h2 id="fortschritt" tabindex="-1"><a class="header-anchor" href="#fortschritt"><span>Fortschritt</span></a></h2><h3 id="gesamtprojekt" tabindex="-1"><a class="header-anchor" href="#gesamtprojekt"><span>Gesamtprojekt</span></a></h3><div class="language- line-numbers-mode" data-highlighter="shiki" data-ext="" data-title="" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span>Phase 0: ██████████ 100% (6/6 Aufgaben) ✅</span></span>
|
||
<span class="line"><span>Phase 1: ██████████ 100% (6/6 Aufgaben) ✅</span></span>
|
||
<span class="line"><span>Phase 2: ██████████ 100% (26/26 Aufgaben) ✅</span></span>
|
||
<span class="line"><span>Phase 3: ██████████ 100% (24/24 Aufgaben) ✅ - Webapp-Integration komplett</span></span>
|
||
<span class="line"><span>Phase 4: ██████░░░░ 63% (17/27 Aufgaben) - Tier 1 ✅, Tier A ✅, Infra ✅, OsBadge ✅, ds-grid ✅, ds-table→HTML ✅, OsNumber ✅ | Tier B (rest), Tier 2-3 ausstehend</span></span>
|
||
<span class="line"><span>Phase 5: ░░░░░░░░░░ 0% (0/7 Aufgaben)</span></span>
|
||
<span class="line"><span>───────────────────────────────────────</span></span>
|
||
<span class="line"><span>Gesamt: ████████░░ 82% (79/96 Aufgaben)</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="katalogisierung-details-in-katalog-md" tabindex="-1"><a class="header-anchor" href="#katalogisierung-details-in-katalog-md"><span>Katalogisierung (Details in KATALOG.md)</span></a></h3><div class="language- line-numbers-mode" data-highlighter="shiki" data-ext="" data-title="" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span>Webapp: ██████████ 100% (139 Komponenten erfasst)</span></span>
|
||
<span class="line"><span>Styleguide: ██████████ 100% (38 Komponenten erfasst)</span></span>
|
||
<span class="line"><span>Analyse: ██████████ 100% (Button, Modal, Menu detailiert)</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="osbutton-migration-phase-3-✅" tabindex="-1"><a class="header-anchor" href="#osbutton-migration-phase-3-✅"><span>OsButton Migration (Phase 3) ✅</span></a></h3><div class="language- line-numbers-mode" data-highlighter="shiki" data-ext="" data-title="" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span>Scope gesamt: 133 <os-button> Tags in 79 Webapp-Dateien</span></span>
|
||
<span class="line"><span>├─ Migriert: 133 Buttons (100%) ✅</span></span>
|
||
<span class="line"><span>├─ <base-button>: 0 verbleibend in Templates</span></span>
|
||
<span class="line"><span>├─ <ds-button>: 0 verbleibend in Templates</span></span>
|
||
<span class="line"><span>└─ Cleanup: Snapshots/Tests müssen aktualisiert werden</span></span>
|
||
<span class="line"><span></span></span>
|
||
<span class="line"><span>OsButton Features:</span></span>
|
||
<span class="line"><span>├─ variant: ✅ primary, secondary, danger, warning, success, info, default</span></span>
|
||
<span class="line"><span>├─ appearance: ✅ filled, outline, ghost</span></span>
|
||
<span class="line"><span>├─ size: ✅ sm, md, lg, xl</span></span>
|
||
<span class="line"><span>├─ disabled: ✅ mit hover/active-Override (nur as="button")</span></span>
|
||
<span class="line"><span>├─ icon: ✅ slot-basiert (icon-system-agnostisch)</span></span>
|
||
<span class="line"><span>├─ circle: ✅ rounded-full, größenabhängig (p-1.5 bis p-3)</span></span>
|
||
<span class="line"><span>├─ loading: ✅ animated SVG spinner, aria-busy (Milestone 4b)</span></span>
|
||
<span class="line"><span>└─ as: ✅ polymorphes Rendering (button/a/NuxtLink/RouterLink)</span></span>
|
||
<span class="line"><span></span></span>
|
||
<span class="line"><span>as-Prop Migration: 15 <nuxt-link>/<a>-Wrapper in 15 Webapp-Dateien → as="nuxt-link"/as="a"</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="osicon-phase-4" tabindex="-1"><a class="header-anchor" href="#osicon-phase-4"><span>OsIcon (Phase 4)</span></a></h3><div class="language- line-numbers-mode" data-highlighter="shiki" data-ext="" data-title="" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span>OsIcon Features:</span></span>
|
||
<span class="line"><span>├─ name: ✅ System-Icon per Name (check, close, plus)</span></span>
|
||
<span class="line"><span>├─ icon: ✅ Custom Vue-Komponente (hat Vorrang vor name)</span></span>
|
||
<span class="line"><span>├─ size: ✅ xs, sm, md, lg, xl, 2xl (em-basiert)</span></span>
|
||
<span class="line"><span>├─ a11y: ✅ decorative (default) / semantic (mit aria-label)</span></span>
|
||
<span class="line"><span>├─ color: ✅ fill-current (erbt von Parent)</span></span>
|
||
<span class="line"><span>└─ svg-plugin: ✅ vite-svg-icon (SVG → Vue Component via ?icon)</span></span>
|
||
<span class="line"><span></span></span>
|
||
<span class="line"><span>System-Icons:</span></span>
|
||
<span class="line"><span>├─ check.svg (Checkmark)</span></span>
|
||
<span class="line"><span>├─ close.svg (Close/X)</span></span>
|
||
<span class="line"><span>└─ plus.svg (Plus/Add)</span></span>
|
||
<span class="line"><span></span></span>
|
||
<span class="line"><span>Ocelot-Icons (separates Entry-Point):</span></span>
|
||
<span class="line"><span>└─ 82 Icons (Feature-Icons + Kategorie-Icons aus Webapp migriert)</span></span>
|
||
<span class="line"><span></span></span>
|
||
<span class="line"><span>OsSpinner:</span></span>
|
||
<span class="line"><span>├─ size: ✅ xs, sm, md, lg, xl, 2xl (em-basiert)</span></span>
|
||
<span class="line"><span>├─ color: ✅ currentColor (erbt von Parent)</span></span>
|
||
<span class="line"><span>├─ a11y: ✅ role="status", aria-label="Loading" (customizable)</span></span>
|
||
<span class="line"><span>├─ decorative: ✅ aria-hidden="true" suppresses role/aria-label</span></span>
|
||
<span class="line"><span>├─ os-button: ✅ OsButton nutzt OsSpinner als Komponente (decorative)</span></span>
|
||
<span class="line"><span>├─ vue-compat: ✅ h() Render-Function mit isVue2</span></span>
|
||
<span class="line"><span>└─ webapp: ✅ 4 Spinner migriert (DsSpinner + LoadingSpinner → OsSpinner)</span></span>
|
||
<span class="line"><span></span></span>
|
||
<span class="line"><span>BaseCard → OsCard Webapp-Migration: ✅</span></span>
|
||
<span class="line"><span>├─ ~30 Webapp-Dateien: <base-card> → <os-card> (lokale Imports)</span></span>
|
||
<span class="line"><span>├─ 3 Template-Dateien: #imageColumn/#topMenu Slots → inline Layout mit --columns CSS</span></span>
|
||
<span class="line"><span>├─ 16 Spec-Dateien: wrapper.classes('base-card') → wrapper.classes('os-card')</span></span>
|
||
<span class="line"><span>├─ 4 Story-Dateien: <base-card> → <os-card> mit Import</span></span>
|
||
<span class="line"><span>├─ 12 Cypress E2E-Dateien: .base-card → .os-card Selektoren</span></span>
|
||
<span class="line"><span>├─ 2 Cypress-Dateien: .hero-image → .os-card__hero-image</span></span>
|
||
<span class="line"><span>├─ BaseCard.vue Komponente gelöscht</span></span>
|
||
<span class="line"><span>├─ base-components.js Plugin gelöscht (keine Base*.vue mehr)</span></span>
|
||
<span class="line"><span>├─ nuxt.config.js, maintenance config, testSetup.js bereinigt</span></span>
|
||
<span class="line"><span>├─ main.scss: .os-card Regeln (title, ds-section, hero-image, --columns Layout)</span></span>
|
||
<span class="line"><span>├─ CSS Fixes: Tailwind p-6 Override (!important), outline statt border (highlight),</span></span>
|
||
<span class="line"><span>│ child selectors → descendant selectors (hero-image content wrapper)</span></span>
|
||
<span class="line"><span>├─ ContributionForm: Media-Query Selektoren auf .os-card__content korrigiert</span></span>
|
||
<span class="line"><span>├─ ProfileList: .profile-list.os-card Spezifität erhöht (0,3,0 vs 0,2,0)</span></span>
|
||
<span class="line"><span>└─ 0 <base-card> Template-Nutzungen verbleibend</span></span>
|
||
<span class="line"><span></span></span>
|
||
<span class="line"><span>DsSpinner/LoadingSpinner → OsSpinner Webapp-Migration: ✅</span></span>
|
||
<span class="line"><span>├─ ImageUploader.vue: LoadingSpinner → OsSpinner (size="lg")</span></span>
|
||
<span class="line"><span>├─ pages/profile: ds-spinner → os-spinner (size="lg")</span></span>
|
||
<span class="line"><span>├─ pages/groups: ds-spinner → os-spinner (size="lg")</span></span>
|
||
<span class="line"><span>├─ pages/admin: ds-spinner → os-spinner (size="xl") + ApolloQuery→apollo Option</span></span>
|
||
<span class="line"><span>├─ LoadingSpinner Komponente gelöscht</span></span>
|
||
<span class="line"><span>├─ ds-space centered → div+padding (Bugfix in 3 Seiten)</span></span>
|
||
<span class="line"><span>├─ Admin: ApolloQuery→$apollo.loading (Spinner war wg. SSR-Prefetch unsichtbar)</span></span>
|
||
<span class="line"><span>└─ infinite-loading: OsSpinner im spinner-Slot (index, profile, groups)</span></span>
|
||
<span class="line"><span></span></span>
|
||
<span class="line"><span>BaseIcon → OsIcon Webapp-Migration: ✅</span></span>
|
||
<span class="line"><span>├─ 131 <base-icon> in 70+ Dateien → <os-icon :icon="..."></span></span>
|
||
<span class="line"><span>├─ 82 SVGs in ocelot/icons/svgs/ (inkl. 17 Kategorie-Icons)</span></span>
|
||
<span class="line"><span>├─ vite-svg-icon Plugin erweitert (rect, circle, polygon, polyline, ellipse, line)</span></span>
|
||
<span class="line"><span>├─ Kategorie-Icons: DB-String → toCamelCase() → ocelotIcons Lookup</span></span>
|
||
<span class="line"><span>├─ Jest Mocks: @ocelot-social/ui/ocelot für ocelotIcons in Tests</span></span>
|
||
<span class="line"><span>├─ Tests aktualisiert: 911/939 Tests bestanden (3 pre-existing failures)</span></span>
|
||
<span class="line"><span>└─ 0 base-icon/BaseIcon Referenzen verbleibend</span></span>
|
||
<span class="line"><span></span></span>
|
||
<span class="line"><span>Tier A ds-* → Plain HTML + CSS: ✅</span></span>
|
||
<span class="line"><span>├─ 10 triviale Vue-Wrapper-Komponenten durch HTML-Elemente + CSS-Klassen ersetzt</span></span>
|
||
<span class="line"><span>├─ ~450 Nutzungen in ~90 Dateien migriert</span></span>
|
||
<span class="line"><span>├─ _ds-compat.scss: Utility-Klassen für Margins (.ds-mb-*, .ds-mt-*, .ds-my-*),</span></span>
|
||
<span class="line"><span>│ Flex (.ds-flex, .ds-flex-item, .ds-flex-gap-*), Centered (.ds-space-centered)</span></span>
|
||
<span class="line"><span>├─ ds-flex/ds-flex-item: JavaScript window.innerWidth → CSS @media Queries</span></span>
|
||
<span class="line"><span>│ (kein Layout-Shift bei SSR, bessere Performance)</span></span>
|
||
<span class="line"><span>├─ system.css bleibt geladen — bestehende CSS-Klassen funktionieren weiter</span></span>
|
||
<span class="line"><span>├─ Verbleibend: 8 ds-* Komponenten (Tier B Rest: 1 einfache, Tier C: 6 komplexe → UI-Library)</span></span>
|
||
<span class="line"><span>└─ 0 Tier-A ds-* Komponenten-Tags verbleibend</span></span>
|
||
<span class="line"><span></span></span>
|
||
<span class="line"><span>ds-number → OsNumber (UI-Library): ✅</span></span>
|
||
<span class="line"><span>├─ OsNumber Komponente: h() Render-Function, requestAnimationFrame Animation, inheritAttrs: false</span></span>
|
||
<span class="line"><span>├─ Props: count (required), label (optional), animated (optional)</span></span>
|
||
<span class="line"><span>├─ Animation: 1500ms ease-out, watch(count) re-animiert, SSR-safe (onMounted)</span></span>
|
||
<span class="line"><span>├─ Styling: tabular-nums + min-width für stabile Breite, --color-text-soft Label-Farbe</span></span>
|
||
<span class="line"><span>├─ ds-number + CountTo: 5 Dateien → <os-number> (UserTeaserPopover, TabNavigation, admin, profile, groups)</span></span>
|
||
<span class="line"><span>├─ vue-count-to Dependency entfernt, CountTo.vue gelöscht</span></span>
|
||
<span class="line"><span>├─ CSS-Variable: --color-text-soft in requiredCssVariables + ocelot-ui-variables.scss</span></span>
|
||
<span class="line"><span>├─ 11 Unit-Tests, 5 Stories, 5 Visual Tests + 1 Keyboard Test</span></span>
|
||
<span class="line"><span>└─ 0 ds-number/CountTo Nutzungen verbleibend</span></span>
|
||
<span class="line"><span></span></span>
|
||
<span class="line"><span>ds-chip + ds-tag → OsBadge (UI-Library): ✅</span></span>
|
||
<span class="line"><span>├─ OsBadge Komponente: CVA-Varianten, h() Render-Function, inheritAttrs: false</span></span>
|
||
<span class="line"><span>├─ Props: variant (default/primary/danger), size (sm/md/lg), shape (pill/square)</span></span>
|
||
<span class="line"><span>├─ Types: BadgeVariant, BadgeSize, BadgeShape (+ BadgeVariants)</span></span>
|
||
<span class="line"><span>├─ ds-chip: 20 Nutzungen in 5 Dateien → <os-badge> (Formzähler + Gruppen-Metadaten)</span></span>
|
||
<span class="line"><span>├─ ds-tag: 3 Nutzungen in 3 Dateien → <os-badge shape="square"> (Category, Hashtag)</span></span>
|
||
<span class="line"><span>├─ ARIA: role="status" aria-live="polite" auf Form-Zähler (11 Stellen in 2 Dateien)</span></span>
|
||
<span class="line"><span>├─ CSS-Variable: --color-default + --color-default-contrast (beide in requiredCssVariables)</span></span>
|
||
<span class="line"><span>├─ 18 Unit-Tests, 6 Stories, 5 Visual Tests + 1 Keyboard Test</span></span>
|
||
<span class="line"><span>└─ 0 ds-chip/ds-tag Nutzungen verbleibend</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><hr><h2 id="aktueller-stand" tabindex="-1"><a class="header-anchor" href="#aktueller-stand"><span>Aktueller Stand</span></a></h2><p><strong>Letzte Aktualisierung:</strong> 2026-02-20 (Session 32)</p><p><strong>Aktuelle Phase:</strong> Phase 4 - Tier 1 ✅, Tier A ✅, OsBadge ✅, ds-grid ✅, ds-table→HTML ✅, OsNumber ✅ | Tier B (rest), Tier 2-3 ausstehend</p><p><strong>Zuletzt abgeschlossen (Session 32 - OsNumber: ds-number + CountTo → OsNumber):</strong></p><ul><li>[x] OsNumber Komponente in packages/ui erstellt (h() Render-Function, requestAnimationFrame Animation)</li><li>[x] Props: count (required), label (optional), animated (optional, 1500ms ease-out)</li><li>[x] Animation: requestAnimationFrame-Loop, watch(count) re-animiert von oldVal→newVal</li><li>[x] Stabile Breite: <code>tabular-nums</code> + <code>min-width: Nch</code> basierend auf Zielwert-Ziffernanzahl</li><li>[x] CSS-Variable <code>--color-text-soft</code> in tailwind.preset.ts (requiredCssVariables), Storybook-Theme, ocelot-ui-variables.scss</li><li>[x] 5 Webapp-Dateien migriert: UserTeaserPopover (statisch), TabNavigation (animated), admin/index (animated), profile/_slug (animated), groups/_slug (animated)</li><li>[x] CountTo.vue gelöscht, <code>vue-count-to</code> Dependency aus package.json entfernt</li><li>[x] <code>followedByCountStartValue</code> / <code>membersCountStartValue</code> Pattern entfernt (OsNumber watch-basiert)</li><li>[x] ds-number CSS aus <code>_ds-compat.scss</code> entfernt</li><li>[x] Admin-Dashboard: <code>.os-number-label { text-transform: uppercase }</code> per CSS (kein neuer Prop)</li><li>[x] Test-Fixes: Fehlende Count-Properties in Mock-Daten (followedByCount, contributionsCount, membersCount etc.)</li><li>[x] 11 Unit-Tests, 5 Stories, 5 Visual + A11y Tests</li></ul><p><strong>Zuvor abgeschlossen (Session 31 - ds-table → Plain HTML):</strong></p><ul><li>[x] ds-table (7 Nutzungen) → native <code><table></code> + CSS-Klassen (kein OsTable nötig)</li><li>[x] Table-CSS in <code>_ds-compat.scss</code>: .ds-table-wrap, .ds-table, .ds-table-col, .ds-table-head-col, .ds-table-bordered, .ds-table-condensed, Alignment-Klassen</li><li>[x] <code>fields()</code> / <code>tableFields()</code> Computed Properties aus allen 7 Dateien entfernt (Labels direkt in <code><th></code>)</li><li>[x] Alle 16 Tests bestanden (3 Test-Suites: admin/users Snapshots aktualisiert, FiledReportsTable ✅, ReportsTable ✅)</li></ul><p><strong>Zuvor abgeschlossen (Session 30 - OsBadge Code-Review Fixes):</strong></p><ul><li>[x] <code>--color-default-contrast</code> zu <code>requiredCssVariables</code> in tailwind.preset.ts hinzugefügt</li><li>[x] Doppelte <code>--color-default</code>-Deklaration in ocelot-ui-variables.scss entfernt (softest → softer konsolidiert)</li><li>[x] Redundante Ternär-Ausdrücke entfernt: GroupTeaser.vue + groups/_slug.vue (<code>group ? group.about : ''</code> → <code>group.about</code>)</li><li>[x] <code>BadgeVariant</code> Typ hinzugefügt und in OsBadge.vue verwendet (statt <code>NonNullable<BadgeVariants['variant']></code>)</li><li>[x] Exports erweitert: BadgeVariant in index.ts + components/index.ts</li><li>[x] GroupForm.vue: <code>float: right</code> → <code>display: flex; flex-direction: column; align-self: flex-end</code> (konsistentes Flexbox-Layout)</li><li>[x] PlaygroundArgs in Stories: <code>string</code> → <code>BadgeVariant | BadgeSize | BadgeShape</code> (typsichere Story-Args)</li><li>[x] WithIcon-Story: Inline-Style <code>style="margin-right: 4px"</code> → Tailwind <code>class="mr-1"</code></li><li>[x] ARIA Live Regions: <code>role="status" aria-live="polite"</code> auf 10 Form-Zähler + <code>role="alert" aria-live="assertive"</code> auf 1 Fehler-Badge</li><li>[x] <code>live</code> Prop entfernt — Standard-ARIA-Attribute werden direkt durchgereicht (attrs)</li><li>[x] KATALOG.md: Übersichtstabelle, Tier B Status, Komponenten-Tabelle, Zusammenfassungen aktualisiert</li></ul><p><strong>Zuvor abgeschlossen (Session 29 - OsBadge: ds-chip + ds-tag Migration):</strong></p><ul><li>[x] OsBadge Komponente in packages/ui erstellt (CVA-Varianten, h() Render-Function)</li><li>[x] Props: variant (default/primary/danger), size (sm/md/lg), shape (pill/square)</li><li>[x] ds-chip → OsBadge: 20 Nutzungen in 5 Webapp-Dateien (GroupTeaser, GroupMember, GroupForm, ContributionForm, groups/_slug)</li><li>[x] ds-tag → OsBadge: 3 Nutzungen in 3 Webapp-Dateien (Category, Hashtag, PostTeaser CSS)</li><li>[x] CSS-Variable --color-default für neutralen Badge-Hintergrund</li><li>[x] Unit-Tests (16), Stories (6), Visual Tests (5+1 Keyboard), 100% Coverage</li><li>[x] Exports: OsBadge, badgeVariants, BadgeShape, BadgeSize, BadgeVariants</li></ul><p><em><em>Zuvor abgeschlossen (Session 27 - Tier A: ds-</em> Komponenten → Plain HTML):</em>*</p><ul><li>[x] <code>_ds-compat.scss</code> erstellt (Utility-Klassen für Margins, Flex, Centered)</li><li>[x] <code>ds-section</code> → <code><section class="ds-section"></code> (5 Dateien)</li><li>[x] <code>ds-placeholder</code> → <code><div class="ds-placeholder"></code> (4 Dateien)</li><li>[x] <code>ds-tag</code> → <code><span class="ds-tag"></code> (2 Dateien)</li><li>[x] <code>ds-list</code> → <code><ul class="ds-list"></code> (4 Dateien)</li><li>[x] <code>ds-list-item</code> → <code><li class="ds-list-item"></code> (8 Dateien)</li><li>[x] <code>ds-container</code> → <code><div class="ds-container ds-container-{width}"></code> (14 Dateien, 12 Dateien)</li><li>[x] <code>ds-heading</code> → <code><h1-h4 class="ds-heading ds-heading-h{n}"></code> (31 Nutzungen, 25 Dateien)</li><li>[x] <code>ds-text</code> → <code><p/div class="ds-text ds-text-{color} ds-text-size-{size}"></code> (80 Nutzungen, 42 Dateien)</li><li>[x] <code>ds-space</code> → <code><div class="ds-mb-{size}"></code> / <code><div class="ds-my-{size}"></code> (139 Nutzungen, 55+ Dateien)</li><li>[x] <code>ds-flex</code> / <code>ds-flex-item</code> → Plain HTML + CSS Media Queries (103 Nutzungen, 29 Dateien)</li><li>[x] Responsive Layouts: JavaScript <code>window.innerWidth</code> → CSS <code>@media</code> Queries (kein Layout-Shift)</li><li>[x] Bugfix: <code><p></code> mit Block-Level-Kindern → <code><div></code> (DateTimeRange.vue, verify.vue)</li><li>[x] Test-Fix: Empty.spec.js <code>attributes().margin</code> → <code>classes().toContain('ds-my-xxx-small')</code></li><li>[x] 0 Tier-A <code>ds-*</code> Komponenten-Tags verbleibend</li></ul><p><em><em>Verbleibende ds-</em> Komponenten (7 Typen):</em>*</p><ul><li>Tier B Rest (→ Plain HTML): ds-radio (1)</li><li>Tier C (→ UI-Library): ds-input (23), ds-form (18), ds-modal (7), ds-menu/ds-menu-item (17), ds-select (3)</li></ul><p><strong>Zuvor abgeschlossen (Session 26 - CodeRabbit Review Fixes):</strong></p><ul><li>[x] Cypress: <code>.os-card .title</code> → <code>.os-card > .title</code> (Kind-Kombinator statt Nachfahren)</li><li>[x] OsCard.spec.ts: <code>compareDocumentPosition()</code> Bitmask-Assertion <code>!== 0</code> statt <code>.toBe(true)</code></li><li>[x] Maintenance-App: <code>@ocelot-social/ui</code> Abhängigkeit + <code>./nuxt.config.js</code> Import als pre-existing (kein Scope) bewertet</li></ul><p><strong>Zuvor abgeschlossen (Session 25 - BaseCard → OsCard Webapp-Migration):</strong></p><ul><li>[x] ~30 Webapp-Dateien: <code><base-card></code> → <code><os-card></code> mit lokalen Imports</li><li>[x] 3 Template-Dateien mit #imageColumn/#topMenu Slots → inline Layout (LoginForm, RegistrationSlider, password-reset)</li><li>[x] CSS: <code>.os-card.--columns</code> Layout in main.scss (flex, image-column, content-column, top-menu, responsive)</li><li>[x] 16 Spec-Dateien: <code>wrapper.classes('base-card')</code> → <code>wrapper.classes('os-card')</code></li><li>[x] 4 Story-Dateien: <code><base-card></code> → <code><os-card></code> mit OsCard-Import</li><li>[x] 12 Cypress E2E Step-Definitions: <code>.base-card</code> → <code>.os-card</code> Selektoren</li><li>[x] 2 Cypress-Dateien: <code>.hero-image</code> → <code>.os-card__hero-image</code></li><li>[x] BaseCard.vue Komponente gelöscht</li><li>[x] <code>base-components.js</code> Plugin gelöscht (keine Base*.vue Komponenten mehr)</li><li>[x] Plugin-Referenzen entfernt: nuxt.config.js, nuxt.config.maintenance.js, testSetup.js</li><li>[x] main.scss bereinigt: <code>.base-card > .ds-section</code> entfernt, <code>.os-card</code> Regeln hinzugefügt</li><li>[x] CSS-Fixes: Tailwind <code>p-6</code> Override (<code>!important</code>), <code>outline</code> statt <code>border</code> (highlight), child → descendant selectors</li><li>[x] ContributionForm: Media-Query Selektoren auf <code>.os-card__content > .buttons-footer</code> korrigiert</li><li>[x] ProfileList: Spezifität <code>.profile-list.os-card</code> erhöht (0,3,0 vs 0,2,0)</li><li>[x] OsCard highlight Tests: <code>border</code> → <code>outline-1</code> (twMerge), Testnamen aktualisiert</li><li>[x] Kleinere Verbesserungen: SocialMedia Props typisiert, LoginForm querySelector statt fragiler DOM-Traversierung, redundante <code><client-only></code> entfernt, NotificationsTable optional chaining</li><li>[x] <code>hasBaseCard</code> Property verbleibt in 4 Dateien (rein semantisch, kein Komponentenbezug)</li></ul><p><strong>Zuvor abgeschlossen (Session 24 - OsSpinner Webapp-Migration + Refactoring):</strong></p><ul><li>[x] OsButton refactored: nutzt <code>h(OsSpinner, { 'aria-hidden': 'true' })</code> statt Inline-SVG</li><li>[x] OsSpinner: Decorative-Modus (<code>aria-hidden="true"</code> unterdrückt role/aria-label)</li><li>[x] <code>ButtonSize</code> Type exportiert (sm/md/lg/xl), <code>types.d.ts</code> Kommentar aktualisiert</li><li>[x] <code>createSpinnerSvg.ts</code> wieder in OsSpinner.vue inlined (nur noch 1 Nutzer)</li><li>[x] Webapp-Migration: 4 Spinner ersetzt (ImageUploader, profile, groups, admin)</li><li>[x] LoadingSpinner Komponente gelöscht (ersetzt durch OsSpinner)</li><li>[x] <code><ds-space centered></code> → <code><div style="..."></code> Bugfix in 3 Seiten</li><li>[x] Admin-Seite: <code><ApolloQuery></code> → <code>apollo</code>-Option + <code>$apollo.loading</code> (Spinner war wg. SSR-Prefetch unsichtbar)</li><li>[x] <code>filterStatistics()</code> mutiert nicht mehr Originalobjekt (Destructuring statt <code>delete</code>)</li><li>[x] <code>infinite-loading</code> Spinner-Slot: OsSpinner in allen 3 Nutzungen (index, profile, groups)</li><li>[x] Einheitliches Spinner-Design: vue-infinite-loading Default-Spinner → OsSpinner</li></ul><p><strong>Zuvor abgeschlossen (Session 23 - OsSpinner Komponente):</strong></p><ul><li>[x] OsSpinner Komponente implementiert (size prop, currentColor, role="status", aria-label)</li><li>[x] Vue 2/3 kompatibel via <code>h()</code> Render-Function mit <code>isVue2</code></li><li>[x] Storybook Stories: Playground, AllSizes, InheritColor, InlineWithText</li><li>[x] Unit Tests: 23 Tests (rendering, size, accessibility, decorative mode, css, keyboard)</li><li>[x] Visual Tests: 4 Tests (all-sizes, inherit-color, inline-text, keyboard a11y)</li><li>[x] Accessibility: <code>role="status"</code>, <code>aria-label="Loading"</code> (customizable), axe-core checks</li><li>[x] 100% Test-Coverage (Statements, Branches, Functions, Lines)</li><li>[x] Completeness Check bestanden</li><li>[x] OsButton Visual Tests: 19/19 bestanden (kein Regression durch Refactoring)</li></ul><p><strong>Zuvor abgeschlossen (Session 22 - BaseIcon → OsIcon Webapp-Migration):</strong></p><ul><li>[x] 131 <code><base-icon></code> Nutzungen in 70+ Dateien → <code><os-icon :icon="icons.xxx"></code> migriert</li><li>[x] 82 Ocelot-Icons in <code>packages/ui/src/ocelot/icons/svgs/</code> (von 1 auf 82)</li><li>[x] 17 Kategorie-Icons aus Webapp kopiert (networking, energy, psyche, movement, finance, child, mobility, shopping-cart, peace, politics, nature, science, health, media, spirituality, culture, miscellaneous)</li><li>[x] vite-svg-icon Plugin erweitert: unterstützt <code><rect></code>, <code><circle></code>, <code><polygon></code>, <code><polyline></code>, <code><ellipse></code>, <code><line></code> (war path-only)</li><li>[x] Alle neuen SVGs auf Single-Line minifiziert (Multiline brach JS-String-Literale)</li><li>[x] Kategorie-Icons: DB-String → <code>toCamelCase()</code> → <code>ocelotIcons[key]</code> Lookup (Category/index.vue, CategoriesFilter.vue, CategoriesSelect.vue, admin/categories.vue)</li><li>[x] <code>created() { this.icons = ocelotIcons }</code> Pattern in allen Komponenten (non-reactive)</li><li>[x] MenuLegend.vue: <code>legendItems</code> von <code>data()</code> → <code>computed</code> (data() läuft vor created(), this.icons undefined)</li><li>[x] HeaderMenu.vue: Map-Button Icon-Größe korrigiert (<code>size="xl"</code> + negative Margin)</li><li>[x] ShowPassword.vue: <code>:data-test="iconName"</code> entfernt (Icon ist jetzt Render-Function, kein String)</li><li>[x] Jest-Tests aktualisiert: OsIcon + ocelotIcons statt BaseIcon + String-Namen <ul><li>Category/index.spec.js, ProfileAvatar.spec.js, CounterIcon.spec.js, ReportRow.spec.js</li><li>ActionButton.spec.js, ComponentSlider.spec.js, ShowPassword.spec.js, LoginForm.spec.js</li></ul></li><li>[x] 8 stale Snapshot-Dateien gelöscht</li><li>[x] Jest Mock: <code>test/__mocks__/@ocelot-social/ui/ocelot.js</code> für ocelotIcons in Tests</li><li>[x] CSS: <code>.base-icon</code> → <code>.os-icon</code> in main.scss und Category/index.vue</li><li>[x] 0 <code>base-icon</code>/<code>BaseIcon</code> Referenzen verbleibend in gesamter Webapp</li><li>[x] 911/939 Tests bestanden (3 pre-existing Jest worker crashes)</li></ul><p><strong>Zuvor abgeschlossen (Session 21 - OsIcon Komponente, System-Icons, Ocelot-Umbenennung):</strong></p><ul><li>[x] OsIcon Komponente implementiert (name, icon, size Props; Vue 2/3 via vue-demi h())</li><li>[x] System-Icons: check, close, plus (SVG, viewBox 0 0 32 32, stroke-basiert)</li><li>[x] Custom vite-svg-icon Plugin: SVG → Vue Render-Function via <code>?icon</code> Query</li><li>[x] Ocelot-Icons: separates Entry-Point (ocelot.mjs) mit dynamischem Loading via import.meta.glob</li><li>[x] <code>src/webapp/</code> → <code>src/ocelot/</code> umbenannt (konsistentes Naming)</li><li>[x] 100% Test-Coverage für OsIcon</li></ul><p><strong>Zuvor abgeschlossen (Session 20 - <code>as</code>-Prop + nuxt-link Migration):</strong></p><ul><li>[x] OsButton: <code>as</code> Prop implementiert (polymorphe Komponente: <code>button</code>, <code>a</code>, <code>nuxt-link</code>, <code>router-link</code>, Custom-Komponenten)</li><li>[x] Naming-Konvention: <code>tag</code> → <code>as</code> (moderner Standard: Headless UI, Radix Vue, Chakra UI, PrimeVue)</li><li>[x] <code>disabled</code>/<code>type</code>/<code>loading</code> nur bei <code>as="button"</code> (Links haben kein natives <code>disabled</code>-Attribut)</li><li>[x] Stories: <code>Polymorphic</code> Story + Playground <code>as</code>-Selektor (button/a)</li><li>[x] Visual Test: <code>polymorphic</code> Screenshot + a11y-Check</li><li>[x] 15 <code><nuxt-link></code>/<code><a></code>-Wrapper in 15 Webapp-Dateien → <code>as="nuxt-link"</code> / <code>as="a"</code> migriert: <ul><li>GroupButton.vue, CtaUnblockAuthor.vue, terms-and-conditions-confirm.vue</li><li>CustomButton.vue (v-if/v-else → computed <code>linkTag</code>/<code>linkProps</code> konsolidiert)</li><li>groups/index.vue, GroupForm.vue, admin/users/index.vue</li><li>pages/index.vue (CSS <code>button.post-add-button-*</code> → <code>.post-add-button-*</code>)</li><li>profile/_id/_slug.vue (v-if auf ds-grid-item, symmetrisches Padding <code>$space-x-small</code>)</li><li>groups/_id/_slug.vue, MapButton.vue</li><li>ChatNotificationMenu.vue, Chat.vue, UserTeaserPopover.vue</li><li>NotificationMenu.vue (3 Instanzen, 2 zu einem Button konsolidiert via counter-icon)</li></ul></li><li>[x] Verifiziert: 0 verbleibende <code><nuxt-link></code>/<code><a></code>-Wrapper um <code><os-button></code> in Webapp</li></ul><p><strong>Zuvor erledigt (auf master gemergt):</strong></p><ul><li>[x] <code>os-button</code> CSS-Klasse auf Button-Element für Branding-Kompatibilität (#9211)</li><li>[x] eslint-config-it4c v0.11.2 Update: Flat Config, path alias #src, CSS-Linting (#9233)</li><li>[x] Release @ocelot-social/ui v0.0.2</li><li>[x] Release v3.14.1</li></ul><p><strong>Abgeschlossene Phasen:</strong></p><ul><li>[x] Phase 0: Analyse (177 Komponenten katalogisiert)</li><li>[x] Phase 1: Vue 2.7 Upgrade (2.6.14 → 2.7.16, 979 Tests ✅)</li><li>[x] Phase 2: Projekt-Setup (Vite, vue-demi, Tailwind v4, CVA, Storybook 10, CI/CD, 100% Coverage)</li><li>[x] Phase 3: Webapp-Integration — 133 os-button in 79 Dateien, 0 base-button/ds-button verbleibend</li></ul><p><strong>Zuvor abgeschlossen (Sessions 11-19 — Details im Arbeitsprotokoll §12):</strong></p><ul><li>[x] Session 19: CodeRabbit Review Cleanup, ~30 Bugfixes + A11y-Verbesserungen</li><li>[x] Session 18: CodeRabbit Review data-test Selektoren, ~25 A11y aria-labels, OsButton Refactoring</li><li>[x] Session 16: Letzte ds-button Migration, Bugfixes, data-variant Attribut</li><li>[x] Session 15: Milestone 4c — 59 Buttons migriert, 0 base-button verbleibend</li><li>[x] Session 14: Loading Prop, Circle Prop, Spinner-Architektur, Code-Optimierung</li><li>[x] Session 13: Icon-Slot, Storybook Playground, 6 Icon-Buttons migriert</li><li>[x] Session 12: CSS-Linting, CI-Optimierung, Code-Review Fixes</li><li>[x] Session 11: Wasserfarben-Farbschema, Stories konsolidiert, Keyboard A11y</li></ul><p><strong>Nächste Schritte:</strong></p><ul><li>[x] OsSpinner Webapp-Migration (DsSpinner + LoadingSpinner → OsSpinner) ✅</li><li>[x] OsCard Komponente + BaseCard → OsCard Webapp-Migration ✅</li><li>[x] Tier A: 10 triviale ds-* Wrapper → Plain HTML + CSS ✅</li><li>[x] OsBadge Komponente + ds-chip/ds-tag → OsBadge Webapp-Migration ✅</li><li>[x] OsNumber Komponente + ds-number/CountTo → OsNumber Webapp-Migration ✅</li><li>[ ] Tier B (Rest): ds-radio → Plain HTML</li><li>[ ] Weitere Tier 2 Komponenten (OsModal, OsDropdown, OsAvatar, OsInput)</li><li>[ ] ds-form + ds-input → OsForm + OsInput (stark gekoppelt, 18+23 Dateien)</li><li>[ ] ds-menu / ds-menu-item → OsMenu / OsMenuItem</li><li>[ ] ds-select → OsSelect</li><li>[ ] Browser-Fehler untersuchen: <code>TypeError: Cannot read properties of undefined (reading 'heartO')</code> (ocelotIcons undefined im Browser trotz korrekter Webpack-Aliase)</li></ul><p><strong>Manuelle Setup-Aufgaben (außerhalb Code):</strong></p><ul><li>[ ] <code>NPM_TOKEN</code> als GitHub Secret einrichten (für npm publish in ui-release.yml) <ul><li>npm Token erstellen: https://www.npmjs.com/settings/ocelot-social/tokens</li><li>GitHub Secret: Repository → Settings → Secrets → Actions → New secret</li></ul></li><li>[ ] Storybook auf externem Host deployen (via Webhook) <ul><li>Server einrichten für Storybook-Hosting</li><li>Webhook-Endpoint erstellen (zieht + baut bei Release)</li><li>GitHub Webhook konfigurieren (trigger bei Release)</li></ul></li></ul><hr><h2 id="meilensteine" tabindex="-1"><a class="header-anchor" href="#meilensteine"><span>Meilensteine</span></a></h2><h3 id="phase-0-analyse-katalogisierung-✅" tabindex="-1"><a class="header-anchor" href="#phase-0-analyse-katalogisierung-✅"><span>Phase 0: Analyse & Katalogisierung ✅</span></a></h3><ul><li>[x] Vollständige Katalogisierung Webapp-Komponenten (139 Komponenten)</li><li>[x] Vollständige Katalogisierung Styleguide-Komponenten (38 Komponenten)</li><li>[x] Duplikate identifizieren und dokumentieren (5 direkte + 3 Familien)</li><li>[x] Inkonsistenzen und Probleme erfassen (Button/Modal/Menu analysiert)</li><li>[x] Konsolidierungsplan erstellen (Token-Liste)</li><li>[x] Priorisierung der zu migrierenden Komponenten (15 Komponenten in 4 Tiers)</li></ul><h3 id="phase-1-vue-2-7-upgrade-✅" tabindex="-1"><a class="header-anchor" href="#phase-1-vue-2-7-upgrade-✅"><span>Phase 1: Vue 2.7 Upgrade ✅</span></a></h3><ul><li>[x] Vue 2.6 → Vue 2.7 Upgrade in Webapp (2.6.14 → 2.7.16)</li><li>[x] Abhängigkeiten aktualisieren: <ul><li>[x] vue-template-compiler entfernt (in Vue 2.7 eingebaut)</li><li>[x] @vue/composition-api entfernt (in Vue 2.7 eingebaut)</li><li>[x] @nuxtjs/composition-api entfernt (nicht mehr nötig)</li><li>[x] vue-server-renderer auf 2.7.16 aktualisiert</li></ul></li><li>[x] Tests durchführen: <strong>157 Suites, 979 passed, 87 Snapshots</strong> ✅</li><li>[x] Regressionstests (<code>yarn dev</code> und manuelle Prüfung) ✅</li></ul><h3 id="phase-2-projekt-setup-✅" tabindex="-1"><a class="header-anchor" href="#phase-2-projekt-setup-✅"><span>Phase 2: Projekt-Setup ✅</span></a></h3><ul><li>[x] Vite + Vue 3 Projekt initialisieren</li><li>[x] vue-demi einrichten für Vue 2 Kompatibilität</li><li>[x] Tailwind CSS einrichten (v4 mit @tailwindcss/vite)</li><li>[x] Dual-Build konfigurieren (Tailwind Preset + vorkompilierte CSS)</li><li>[x] CSS Custom Properties Token-System aufsetzen (requiredCssVariables + validateCssVariables)</li><li>[x] Dark Mode Grundstruktur (via Tailwind <code>dark:</code> Prefix, dokumentiert)</li><li>[x] Storybook für Dokumentation einrichten</li><li>[x] Vitest konfigurieren</li><li>[x] eslint-config-it4c einrichten (v0.8.0: TypeScript, Vue 3, Vitest, Prettier)</li><li>[x] npm Package-Struktur (@ocelot-social/ui) mit korrekten exports</li><li>[x] Vue 2/3 Kompatibilitätstests (via Example Apps)</li><li>[x] GitHub Workflows einrichten (ui-lint.yml, ui-test.yml, ui-build.yml, ui-compatibility.yml)</li><li>[x] Docker Setup (Dockerfile, docker-compose, ui-docker.yml Workflow)</li><li>[x] Visual Regression Tests einrichten (Playwright, colocated mit Komponenten)</li><li>[x] Accessibility Tests in Visual Tests integriert (@axe-core/playwright)</li><li>[x] Keyboard Accessibility Tests (describe('keyboard accessibility'))</li><li>[x] ESLint Plugins: vuejs-accessibility, playwright, storybook, jsdoc, @eslint/css</li><li>[x] Bundle Size Check einrichten (size-limit, ui-size.yml)</li><li>[x] Package-Validierung einrichten (publint, arethetypeswrong)</li><li>[x] Example Apps erstellen (vue3-tailwind, vue3-css, vue2-tailwind, vue2-css)</li><li>[x] Kompatibilitätstest-Workflow einrichten (4er-Matrix, siehe §18)</li><li>[x] release-please Manifest-Konfiguration</li><li>[x] npm Publish Workflow (ui-release.yml)</li><li>[x] Storybook Build Workflow (ui-storybook.yml)</li><li>[x] LICENSE Datei (Apache 2.0)</li><li>[x] README.md Grundgerüst (Installation, Quick Start, Struktur)</li><li>[x] CONTRIBUTING.md</li><li>[x] Completeness Check Script (Story, Visual+checkA11y, Keyboard, Varianten)</li></ul><h3 id="phase-3-webapp-integration-validierung" tabindex="-1"><a class="header-anchor" href="#phase-3-webapp-integration-validierung"><span>Phase 3: Webapp-Integration (Validierung)</span></a></h3><p><strong>Ziel:</strong> OsButton in der Webapp einbinden, ohne visuelle oder funktionale Änderungen.</p><p><strong>Ansatz:</strong> Integration First - Library einbinden, dann schrittweise OsButton ersetzen, beginnend mit einfachsten Stellen.</p><p><strong>Milestone 1: Library-Einbindung</strong> ✅</p><ul><li>[x] @ocelot-social/ui in Webapp installieren (yarn link + Webpack-Alias)</li><li>[x] vue-demi zur Webapp hinzugefügt (für Vue 2.7 Kompatibilität)</li><li>[x] Webpack-Alias für vue-demi (nutzt Webapp's Vue 2.7)</li><li>[x] Webpack-Alias für @ocelot-social/ui$ und style.css$</li><li>[x] CSS Custom Properties in Webapp definieren (ocelot-ui-variables.scss)</li><li>[x] CSS-Reihenfolge angepasst (UI-Library nach Styleguide)</li><li>[x] Import-Pfade testen</li><li>[x] Docker Build Stage für UI-Library (Dockerfile + Dockerfile.maintenance)</li><li>[x] Relativer Pfad für CI-Kompatibilität (file:../packages/ui)</li><li>[x] Jest Mock für @ocelot-social/ui (test/<strong>mocks</strong>/@ocelot-social/ui.js)</li></ul><p><strong>Milestone 2: Erste Integration (Minimaler Aufwand)</strong> ✅</p><ul><li>[x] OsButton mit isVue2 Render-Funktion (Vue 2/3 kompatibel)</li><li>[x] Button-Variants an ds-button angepasst (font-semibold, rounded, box-shadow)</li><li>[x] OsButton in UserTeaserPopover.vue eingesetzt (<code>variant="primary"</code>)</li><li>[x] Manueller visueller Vergleich ✅</li><li>[x] Webapp-Tests bestehen ✅ (979 Tests, jest moduleNameMapper für vue-demi)</li></ul><p><strong>Milestone 3: Schrittweise Erweiterung</strong> ✅</p><ul><li>[x] GroupForm.vue Cancel-Button migriert</li><li>[x] OsButton attrs/listeners Forwarding (Vue 2 $listeners via getCurrentInstance)</li><li>[x] 14 weitere Buttons migriert (alle ohne icon/circle/loading)</li></ul><p><strong>Milestone 4a: Weitere Buttons migrieren (14 ohne neue Props)</strong> ✅</p><ul><li>[x] Modal Cancel-Buttons (DisableModal, DeleteUserModal, ReleaseModal)</li><li>[x] Form Cancel/Submit-Buttons (ContributionForm, EnterNonce, MySomethingList)</li><li>[x] ImageUploader.vue (2× Crop-Buttons)</li><li>[x] Page-Buttons (donations, badges, notifications/index, profile Unblock/Unmute)</li><li>[x] ReportRow.vue More-Details-Button</li></ul><p><strong>Milestone 4b: OsButton Props erweitern</strong> ✅</p><ul><li>[x] <code>icon</code> Slot implementiert (slot-basiert, icon-system-agnostisch) ✅</li><li>[x] <code>circle</code> Prop implementiert (rounded-full, größenabhängige Breiten) ✅</li><li>[x] <code>loading</code> Prop mit animiertem SVG-Spinner implementiert ✅</li></ul><p><strong>Milestone 4c: Buttons mit icon/circle/loading migrieren</strong> ✅ ABGESCHLOSSEN</p><p><em>Button-Komponenten (Wrapper):</em></p><ul><li>[x] Button/JoinLeaveButton.vue (icon, loading) ✅</li><li>[x] Button/FollowButton.vue (icon, loading) ✅</li><li>[x] LoginButton/LoginButton.vue (icon, circle) ✅</li><li>[x] InviteButton/InviteButton.vue (icon, circle) ✅</li><li>[x] EmotionButton/EmotionButton.vue (circle) ✅</li><li>[x] CustomButton/CustomButton.vue (2× circle) ✅</li><li>[x] LabeledButton/LabeledButton.vue (icon, circle) ✅</li></ul><p><em>Navigation & Menus:</em></p><ul><li>[x] ContentMenu/ContentMenu.vue (icon, circle) ✅</li><li>[x] ContentMenu/GroupContentMenu.vue (icon, circle) ✅</li><li>[x] ChatNotificationMenu.vue (circle) ✅</li><li>[x] NotificationMenu.vue (3× icon, circle) ✅</li><li>[x] HeaderMenu/HeaderMenu.vue (icon, circle) ✅</li><li>[x] Map/MapButton.vue (circle) ✅</li></ul><p><em>Editor:</em></p><ul><li>[x] Editor/MenuBar.vue (~11× icon, circle) ✅</li><li>[x] Editor/MenuLegend.vue (2× icon) ✅</li></ul><p><em>Filter & Input:</em></p><ul><li>[x] HashtagsFilter.vue (icon, circle) ✅</li><li>[x] CategoriesSelect.vue (icon) ✅</li><li>[x] SearchableInput.vue (icon, circle) ✅</li><li>[x] Select/LocationSelect.vue (icon) ✅</li><li>[x] PaginationButtons.vue (2× icon, circle) ✅</li></ul><p><em>Chat:</em></p><ul><li>[x] Chat/Chat.vue (2× icon, circle) ✅</li><li>[x] Chat/AddChatRoomByUserSearch.vue (icon, circle) ✅</li></ul><p><em>Forms & Auth:</em></p><ul><li>[x] LoginForm/LoginForm.vue (icon, loading) ✅</li><li>[x] PasswordReset/Request.vue (loading) ✅</li><li>[x] PasswordReset/ChangePassword.vue (loading) ✅</li><li>[x] Password/Change.vue (loading) ✅</li><li>[x] ContributionForm.vue Submit (icon, loading) ✅</li><li>[x] CommentForm/CommentForm.vue (loading) ✅</li></ul><p><em>Modals:</em></p><ul><li>[x] Modal/ConfirmModal.vue (2× icon, loading) ✅</li><li>[x] Modal/ReportModal.vue (2× icon, loading) ✅</li><li>[x] Modal/DisableModal.vue Confirm (icon) ✅</li><li>[x] Modal/DeleteUserModal.vue Confirm (icon) ✅</li><li>[x] Modal/ReleaseModal.vue Confirm (icon) ✅</li></ul><p><em>Features:</em></p><ul><li>[x] ComponentSlider.vue (2× icon) ✅</li><li>[x] MySomethingList.vue (3× icon, circle) ✅</li><li>[x] CreateInvitation.vue (icon, circle) ✅</li><li>[x] Invitation.vue (2× icon, circle) ✅</li><li>[x] ProfileList.vue (loading) ✅</li><li>[x] ReportRow.vue Confirm (icon) ✅</li><li>[x] ImageUploader.vue Delete/Cancel (2× icon, circle) ✅</li><li>[x] CommentCard.vue Reply (icon, circle) ✅</li><li>[x] EmbedComponent.vue Close (icon, circle) ✅</li><li>[x] CtaUnblockAuthor.vue (icon) ✅</li><li>[x] data-download.vue (icon, loading) ✅</li><li>[x] ActionButton.vue (icon, circle) ✅</li><li>[x] DeleteData.vue (icon) ✅</li><li>[x] GroupButton.vue (icon, circle) ✅</li></ul><p><em>Filter-Menüs:</em></p><ul><li>[x] FilterMenu/FilterMenu.vue (icon) ✅</li><li>[x] FilterMenu/HeaderButton.vue (2× icon) ✅</li><li>[x] FilterMenu/CategoriesFilter.vue (2× icon) ✅</li><li>[x] FilterMenu/OrderByFilter.vue (2×) ✅</li><li>[x] FilterMenu/EventsByFilter.vue (2×) ✅</li><li>[x] FilterMenu/FollowingFilter.vue (3×) ✅</li></ul><p><em>Pages:</em></p><ul><li>[x] pages/index.vue (2× icon, circle) ✅</li><li>[x] pages/groups/index.vue (icon, circle) ✅</li><li>[x] pages/groups/_id/_slug.vue (3× icon, circle) ✅</li><li>[x] pages/admin/users/index.vue (2× icon, circle) ✅</li><li>[x] pages/settings/index.vue (icon) ✅</li><li>[x] pages/settings/blocked-users.vue (icon, circle) ✅</li><li>[x] pages/settings/muted-users.vue (icon, circle) ✅</li><li>[x] pages/settings/data-download.vue (icon) ✅</li><li>[x] pages/settings/my-email-address/index.vue (icon) ✅</li><li>[x] pages/settings/my-email-address/enter-nonce.vue (icon) ✅</li><li>[x] pages/profile/_id/_slug.vue (icon, circle) ✅</li><li>[x] pages/post/_id/_slug/index.vue (icon, circle) ✅</li></ul><p><strong>Milestone 5: Validierung & Dokumentation</strong> ✅</p><ul><li>[x] Keine visuellen Änderungen bestätigt (16/16 Buttons validiert)</li><li>[x] Keine funktionalen Änderungen bestätigt</li><li>[x] Disabled-Styles korrigiert (hover/active-Override, Border-Fix)</li><li>[ ] Webapp-Tests bestehen weiterhin (TODO: Snapshots aktualisieren)</li><li>[ ] Erkenntnisse in KATALOG.md dokumentiert</li></ul><p><strong>Einsatzstellen-Übersicht:</strong></p><table><thead><tr><th>Kategorie</th><th>Buttons</th><th>Status</th></tr></thead><tbody><tr><td>✅ Migriert (gesamt)</td><td>133</td><td>79 Dateien</td></tr><tr><td>⬜ <code><base-button></code> verbleibend</td><td>0</td><td>Nur BaseButton.vue Definition + Test-Dateien</td></tr><tr><td>⬜ <code><ds-button></code> verbleibend</td><td>0</td><td>Alle ersetzt</td></tr><tr><td><strong>Gesamt</strong></td><td><strong>133</strong></td><td><strong>100% erledigt</strong> ✅</td></tr></tbody></table><p><strong>Details siehe KATALOG.md</strong> (vollständige Tracking-Tabellen)</p><p><strong>Erfolgskriterien:</strong></p><table><thead><tr><th>Kriterium</th><th>Prüfung</th></tr></thead><tbody><tr><td>Visuell identisch</td><td>Manueller Screenshot-Vergleich</td></tr><tr><td>Funktional identisch</td><td>Click, Disabled funktionieren</td></tr><tr><td>Keine Regression</td><td>Webapp Unit-Tests bestehen</td></tr></tbody></table><p><strong>Visuelle Validierung (OsButton vs Original):</strong></p><p>Jeder migrierte Button muss manuell geprüft werden: Normal, Hover, Focus, Active, Disabled.</p><table><thead><tr><th>Datei</th><th>Button</th><th>Props</th><th>Validiert</th></tr></thead><tbody><tr><td><code>components/Group/GroupForm.vue</code></td><td>Cancel</td><td><code>default</code></td><td>✅</td></tr><tr><td><code>components/Group/GroupMember.vue</code></td><td>Remove Member</td><td><code>appearance="outline" variant="primary" size="sm"</code></td><td>✅</td></tr><tr><td><code>components/CommentCard/CommentCard.vue</code></td><td>Show more/less</td><td><code>appearance="ghost" variant="primary" size="sm"</code></td><td>✅</td></tr><tr><td><code>components/UserTeaser/UserTeaserPopover.vue</code></td><td>Open Profile</td><td><code>variant="primary"</code></td><td>✅</td></tr><tr><td><code>components/DonationInfo/DonationInfo.vue</code></td><td>Donate Now</td><td><code>size="sm" variant="primary"</code></td><td>✅</td></tr><tr><td><code>components/Map/MapStylesButtons.vue</code></td><td>Map Styles</td><td><code>:appearance</code> dynamisch + custom CSS</td><td>✅</td></tr><tr><td><code>components/Embed/EmbedComponent.vue</code></td><td>Cancel</td><td><code>appearance="outline" variant="danger"</code> + custom CSS</td><td>✅</td></tr><tr><td><code>components/Embed/EmbedComponent.vue</code></td><td>Play Now</td><td><code>variant="primary"</code> + custom CSS</td><td>✅</td></tr><tr><td><code>pages/terms-and-conditions-confirm.vue</code></td><td>Read T&C</td><td><code>appearance="outline" variant="primary"</code></td><td>✅</td></tr><tr><td><code>pages/terms-and-conditions-confirm.vue</code></td><td>Save</td><td><code>variant="primary"</code> + disabled</td><td>✅</td></tr><tr><td><code>pages/settings/privacy.vue</code></td><td>Save</td><td><code>variant="primary"</code> + disabled</td><td>✅</td></tr><tr><td><code>pages/settings/notifications.vue</code></td><td>Check All</td><td><code>appearance="outline" variant="primary"</code> + disabled</td><td>✅</td></tr><tr><td><code>pages/settings/notifications.vue</code></td><td>Uncheck All</td><td><code>appearance="outline" variant="primary"</code> + disabled</td><td>✅</td></tr><tr><td><code>pages/settings/notifications.vue</code></td><td>Save</td><td><code>variant="primary"</code> + disabled</td><td>✅</td></tr><tr><td><code>pages/settings/embeds.vue</code></td><td>Allow All</td><td><code>appearance="outline" variant="primary"</code> + disabled</td><td>✅</td></tr><tr><td><code>pages/settings/embeds.vue</code></td><td>Deny All</td><td><code>variant="primary"</code> + disabled</td><td>✅</td></tr></tbody></table><p><strong>Validierung abgeschlossen:</strong> 16/16 (100%) ✅</p><p><strong>Nach Abschluss aller Validierungen:</strong></p><ul><li>[ ] Gesamt-Regressionstest durchführen</li><li>[ ] Alle Unit-Tests bestehen</li><li>[ ] Dokumentation aktualisieren</li></ul><h3 id="phase-4-komponenten-migration" tabindex="-1"><a class="header-anchor" href="#phase-4-komponenten-migration"><span>Phase 4: Komponenten-Migration</span></a></h3><p><strong>Tier 1: Kern-Komponenten (UI-Library)</strong> ✅</p><ul><li>[x] OsIcon (vereint DsIcon + BaseIcon) ✅ System-Icons + vite-svg-icon Plugin</li><li>[x] OsSpinner (vereint DsSpinner + LoadingSpinner) ✅ OsButton nutzt OsSpinner als Komponente</li><li>[x] OsSpinner Webapp-Migration ✅ 4 Spinner migriert, LoadingSpinner gelöscht</li><li>[x] OsButton (vereint DsButton + BaseButton) ✅ 133 Buttons in 79 Dateien</li><li>[x] OsCard (vereint DsCard + BaseCard) ✅ ~30 Dateien, BaseCard gelöscht</li></ul><p><em><em>Tier A: Triviale ds-</em> Wrapper → Plain HTML + CSS</em>* ✅</p><ul><li>[x] _ds-compat.scss Utility-Klassen (Margins, Flex, Centered)</li><li>[x] ds-section, ds-placeholder, ds-tag, ds-list, ds-list-item → HTML + CSS-Klassen</li><li>[x] ds-container, ds-heading, ds-text → HTML + CSS-Klassen</li><li>[x] ds-space → div + Margin-Utility-Klassen (139 Nutzungen)</li><li>[x] ds-flex / ds-flex-item → HTML + CSS @media Queries (103 Nutzungen)</li></ul><p><em><em>Tier B: Einfache ds-</em> → Plain HTML / UI-Library</em>*</p><ul><li>[x] ds-chip (5 Dateien) → OsBadge (UI-Library) ✅</li><li>[x] ds-tag (3 Dateien) → OsBadge shape="square" (UI-Library) ✅</li><li>[x] ds-number (5 Dateien) → OsNumber (UI-Library) ✅ + CountTo.vue gelöscht, vue-count-to entfernt</li><li>[x] ds-grid / ds-grid-item (10 Dateien) → CSS Grid ✅</li><li>[ ] ds-radio (1 Datei) → native <code><input type="radio"></code></li></ul><p><strong>Tier 2: Layout & Feedback (UI-Library)</strong></p><ul><li>[ ] OsModal (Basis: DsModal, 7 Dateien)</li><li>[ ] OsDropdown (Basis: Webapp Dropdown)</li><li>[ ] OsAvatar (vereint DsAvatar + ProfileAvatar)</li><li>[ ] OsInput (Basis: DsInput, 23 Dateien — gekoppelt mit ds-form)</li></ul><p><strong>Tier 3: Navigation (UI-Library)</strong></p><ul><li>[ ] OsMenu (Basis: DsMenu, 11 Dateien)</li><li>[ ] OsMenuItem (Basis: DsMenuItem, 6 Dateien)</li></ul><p><strong>Tier 4: Spezial-Komponenten</strong></p><ul><li>[ ] OsSelect (3 Dateien)</li><li>[x] ds-table (7 Dateien) → Plain HTML <code><table></code> + CSS-Klassen ✅ (kein OsTable nötig)</li><li>[ ] ds-form → Plain HTML <code><form></code> oder OsForm (18 Dateien)</li></ul><p><strong>Infrastruktur</strong></p><ul><li>[x] System-Icons einrichten ✅ vite-svg-icon Plugin, 3 System-Icons, Ocelot-Icons Entry-Point</li><li>[x] BaseIcon → OsIcon Webapp-Migration ✅ 131 Nutzungen, 82 Ocelot-Icons, 0 BaseIcon verbleibend</li><li>[ ] CI docs-check Workflow (JSDoc-Coverage, README-Aktualität)</li></ul><blockquote><p><strong>Hinweis:</strong> ds-heading, ds-text, ds-tag wurden zu Plain HTML migriert (Tier A). OsHeading/OsText/OsTag als UI-Library-Komponenten sind daher nicht mehr geplant.</p></blockquote><h3 id="phase-5-finalisierung" tabindex="-1"><a class="header-anchor" href="#phase-5-finalisierung"><span>Phase 5: Finalisierung</span></a></h3><ul><li>[ ] Alle Komponenten migriert und getestet</li><li>[ ] Alte Komponenten aus Vue 2 Projekt entfernt</li><li>[ ] Build als npm Library verifiziert</li><li>[ ] README.md finalisieren (alle Sektionen vollständig)</li><li>[ ] ARCHITECTURE.md erstellen (aus PROJEKT.md §2, §4, §5, §11, §15, §16)</li><li>[ ] PROJEKT.md und KATALOG.md archivieren (docs/archive/)</li><li>[ ] Dokumentation vollständig und CI-geprüft</li></ul><hr><h1 id="vision" tabindex="-1"><a class="header-anchor" href="#vision"><span>VISION</span></a></h1><h2 id="_1-projektziel-vision" tabindex="-1"><a class="header-anchor" href="#_1-projektziel-vision"><span>1. Projektziel & Vision</span></a></h2><p><strong>Kurzbeschreibung:</strong> Neue Vue 3 Komponentenbibliothek aufbauen, die später die Vue 2 Komponenten in der Webapp ersetzen soll.</p><p><strong>Hintergrund:</strong></p><ul><li>Bestehendes Projekt nutzt Vue 2.7 mit Nuxt 2 (Upgrade von 2.6 → 2.7 in Phase 1 erledigt ✅)</li><li>Existierender <code>styleguide</code> Ordner als Git-Submodul (Vue 2, Vue CLI 3)</li><li>Design-Token-System mit Theo vorhanden</li><li>Branding erfolgt über SCSS-Dateien mit Variablen-Overrides</li><li><strong>Problem:</strong> Viele doppelte Komponenten, inkonsistente Styles, nicht konsequent genutztes Design-System</li></ul><p><strong>Vision:</strong> 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.</p><p><strong>Geplanter Ansatz:</strong> Migration vorbereiten - schrittweise neue Komponenten in Vue 3 entwickeln, die das bestehende Design-System respektieren und flexible Branding-Optionen bieten.</p><hr><h1 id="technische-grundlagen" tabindex="-1"><a class="header-anchor" href="#technische-grundlagen"><span>TECHNISCHE GRUNDLAGEN</span></a></h1><h2 id="_2-tech-stack" tabindex="-1"><a class="header-anchor" href="#_2-tech-stack"><span>2. Tech-Stack</span></a></h2><table><thead><tr><th>Komponente</th><th>Entscheidung</th><th>Notizen</th></tr></thead><tbody><tr><td>Framework</td><td><strong>Vue 3 + Vite</strong></td><td>Schnellstes Setup, modernes Tooling</td></tr><tr><td>Build-Tool</td><td><strong>Vite</strong></td><td>Schnelles HMR, einfache Konfiguration</td></tr><tr><td>Dokumentation</td><td><strong>Storybook 10</strong></td><td>Komponenten-Dokumentation mit Vue 3 + Vite</td></tr><tr><td>Styling</td><td><strong>Tailwind CSS</strong></td><td>Mit CSS Custom Properties für Branding</td></tr><tr><td>Testing</td><td><strong>Vitest</strong></td><td>Vite-nativ, Jest-kompatible API</td></tr><tr><td>Paket-Name</td><td><strong>@ocelot-social/ui</strong></td><td>Unter ocelot-social npm Org</td></tr><tr><td>Komponenten-Prefix</td><td><strong>Os</strong></td><td>OsButton, OsCard, etc.</td></tr><tr><td>Vue 2 Kompatibilität</td><td><strong>vue-demi</strong></td><td>Library funktioniert in Vue 2 und Vue 3</td></tr><tr><td>Varianten-System</td><td><strong>CVA</strong></td><td>class-variance-authority für typsichere Prop-Varianten</td></tr><tr><td>Klassen-Merge</td><td><strong>cn()</strong></td><td>clsx + tailwind-merge für Klassen-Kombination</td></tr><tr><td>Linting</td><td><strong>eslint-config-it4c</strong></td><td>Enthält: TypeScript, Vue, Prettier, weitere Regeln</td></tr><tr><td>Release</td><td><strong>release-please</strong></td><td>Automatische Versionen und Changelogs</td></tr><tr><td>Icons</td><td><strong>Hybrid-Architektur</strong></td><td>System-Icons in Library, Feature-Icons in App (siehe §4)</td></tr><tr><td>Browser-Support</td><td><strong>Modern only</strong></td><td>Chrome, Firefox, Safari, Edge (letzte 2 Versionen)</td></tr><tr><td>SSR</td><td><strong>Ja</strong></td><td>Nuxt-kompatibel</td></tr><tr><td>Dark Mode</td><td><strong>Ja, von Anfang an</strong></td><td>Alle Komponenten mit Light/Dark Varianten</td></tr><tr><td>Lizenz</td><td><strong>Apache 2.0</strong></td><td>Permissiv mit Patent-Schutz</td></tr><tr><td>Repository</td><td><strong>Monorepo</strong></td><td>Ocelot-Social/packages/ui/</td></tr></tbody></table><h3 id="konventionen" tabindex="-1"><a class="header-anchor" href="#konventionen"><span>Konventionen</span></a></h3><table><thead><tr><th>Aspekt</th><th>Entscheidung</th><th>Notizen</th></tr></thead><tbody><tr><td>Vue API</td><td><strong><code><script setup></code></strong></td><td>Composition API mit script setup (erfordert Vue 2.7+)</td></tr><tr><td>Sprache</td><td><strong>Englisch</strong></td><td>Code, Comments, Dokumentation</td></tr><tr><td>Package Manager</td><td><strong>npm</strong></td><td>Bereits im Projekt verwendet</td></tr><tr><td>Test Coverage</td><td><strong>100%</strong></td><td>Vollständige Testabdeckung</td></tr><tr><td>Dateinamen</td><td><strong>PascalCase</strong></td><td>OsButton.vue, OsCard.vue</td></tr><tr><td>i18n</td><td><strong>Nur Props</strong></td><td>Keine Default-Texte in Komponenten</td></tr><tr><td>Breakpoints</td><td><strong>Tailwind Standard</strong></td><td>sm:640, md:768, lg:1024, xl:1280, 2xl:1536</td></tr><tr><td>Size Props</td><td><strong>Tailwind-Skala</strong></td><td>sm, md, lg, xl (komponentenspezifisch)</td></tr><tr><td>Rounded Props</td><td><strong>Tailwind-Skala (vollständig)</strong></td><td>none, sm, md, lg, xl, 2xl, 3xl, full</td></tr><tr><td>Shadow Props</td><td><strong>Tailwind-Skala (vollständig)</strong></td><td>none, sm, md, lg, xl, 2xl</td></tr><tr><td>Variant Props</td><td><strong>Semantisch (vollständig)</strong></td><td>primary, secondary, danger, warning, success, info</td></tr><tr><td>Dark Mode</td><td><strong>Tailwind CSS-Klassen</strong></td><td>Via <code>dark:</code> Prefix, kein "inverse" Prop</td></tr><tr><td>Prop-Vollständigkeit</td><td><strong>Alle oder keine</strong></td><td>Wenn Komponente einen Prop unterstützt, dann die gesamte Skala</td></tr><tr><td>CSS Variables</td><td><strong>Keine Defaults in Library</strong></td><td>Webapp definiert Default-Branding, spezialisierte Brandings überschreiben</td></tr><tr><td>TypeScript</td><td><strong>strict: true</strong></td><td>Strikte Typisierung</td></tr></tbody></table><hr><h2 id="_3-build-distribution" tabindex="-1"><a class="header-anchor" href="#_3-build-distribution"><span>3. Build & Distribution</span></a></h2><h3 id="dual-build-strategie" tabindex="-1"><a class="header-anchor" href="#dual-build-strategie"><span>Dual-Build Strategie</span></a></h3><p>Die Library bietet zwei Nutzungsmöglichkeiten:</p><div class="language- line-numbers-mode" data-highlighter="shiki" data-ext="" data-title="" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span>@ocelot-social/ui</span></span>
|
||
<span class="line"><span>├── dist/</span></span>
|
||
<span class="line"><span>│ ├── index.js # Vue Komponenten</span></span>
|
||
<span class="line"><span>│ ├── style.css # Vorkompilierte Styles (für Nicht-Tailwind)</span></span>
|
||
<span class="line"><span>│ └── tailwind.preset.js # Tailwind Preset (für Tailwind-Nutzer)</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="nutzung-mit-tailwind" tabindex="-1"><a class="header-anchor" href="#nutzung-mit-tailwind"><span>Nutzung MIT Tailwind</span></a></h3><div class="language-js line-numbers-mode" data-highlighter="shiki" data-ext="js" data-title="js" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;">// tailwind.config.js</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">import</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> ocelotPreset</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;"> from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> '@ocelot-social/ui/tailwind.preset'</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">export</span><span style="--shiki-light:#E45649;--shiki-dark:#C678DD;"> default</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">presets</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> [</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">ocelotPreset</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">] }</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;">// main.js</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">OsButton</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> '@ocelot-social/ui'</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="nutzung-ohne-tailwind" tabindex="-1"><a class="header-anchor" href="#nutzung-ohne-tailwind"><span>Nutzung OHNE Tailwind</span></a></h3><div class="language-js line-numbers-mode" data-highlighter="shiki" data-ext="js" data-title="js" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;">// main.js</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">OsButton</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> '@ocelot-social/ui'</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">import</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> '@ocelot-social/ui/style.css'</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="branding-funktioniert-fur-beide" tabindex="-1"><a class="header-anchor" href="#branding-funktioniert-fur-beide"><span>Branding (funktioniert für beide)</span></a></h3><div class="language-css line-numbers-mode" data-highlighter="shiki" data-ext="css" data-title="css" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;">/* branding.css */</span></span>
|
||
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#56B6C2;">:root</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> {</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> --color-primary</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;">rgb</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66;">110</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66;">139</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66;">135</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">);</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> --button-primary-bg</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;">var</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">--color-secondary</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">);</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">}</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>Beide Varianten nutzen CSS Custom Properties</strong> - Branding ist identisch.</p><h3 id="webapp-integration-entwicklung" tabindex="-1"><a class="header-anchor" href="#webapp-integration-entwicklung"><span>Webapp-Integration (Entwicklung)</span></a></h3><p><strong>Lokale Entwicklung mit Nuxt Alias:</strong></p><div class="language-js line-numbers-mode" data-highlighter="shiki" data-ext="js" data-title="js" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;">// webapp/nuxt.config.js</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">export</span><span style="--shiki-light:#E45649;--shiki-dark:#C678DD;"> default</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> {</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> alias</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> {</span></span>
|
||
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> '@ocelot-social/ui'</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;"> process</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B;">env</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">.</span><span style="--shiki-light:#986801;--shiki-dark:#E06C75;">LOCAL_UI</span></span>
|
||
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#C678DD;"> ?</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;"> path</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;">resolve</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">__dirname</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">'../packages/ui/src'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">)</span></span>
|
||
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#C678DD;"> :</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> '@ocelot-social/ui'</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> }</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">}</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>Entwickler startet mit:</strong></p><div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" data-title="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">LOCAL_UI</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2;">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">true</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;"> yarn</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> dev</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div></div></div><p><strong>Release-Check:</strong> Bei Ocelot-Release wird geprüft, ob <code>packages/ui</code> unreleased Änderungen hat:</p><div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" data-title="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;"># Commits seit letztem ui-Tag?</span></span>
|
||
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;">git</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> log</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66;"> --oneline</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> $(</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;">git</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> describe</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66;"> --tags</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66;"> --match</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> "ui-v*"</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66;"> --abbrev=0</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">)</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">..HEAD</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66;"> --</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> packages/ui/</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div></div></div><p>→ Wenn Output vorhanden: UI muss zuerst released werden.</p><h3 id="webapp-aufgaben-todo-fur-ocelot-webapp" tabindex="-1"><a class="header-anchor" href="#webapp-aufgaben-todo-fur-ocelot-webapp"><span>Webapp-Aufgaben (TODO für Ocelot-Webapp)</span></a></h3><p>Die folgenden Aufgaben müssen in der Webapp umgesetzt werden, nicht in der UI-Library:</p><table><thead><tr><th>Aufgabe</th><th>Beschreibung</th><th>Status</th></tr></thead><tbody><tr><td><strong>Branding-Validierung</strong></td><td>Test/Workflow der <code>validateCssVariables()</code> aufruft und sicherstellt, dass das Default-Branding alle von der Library geforderten CSS-Variablen definiert</td><td>⏳ Offen</td></tr></tbody></table><p><strong>Beispiel-Implementierung (Webapp):</strong></p><div class="language-ts line-numbers-mode" data-highlighter="shiki" data-ext="ts" data-title="ts" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;">// webapp/tests/branding.spec.ts</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">validateCssVariables</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">requiredCssVariables</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> '@ocelot-social/ui/tailwind.preset'</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;">describe</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">'Default Branding'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, () </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">=></span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> {</span></span>
|
||
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;"> it</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">'defines all required CSS variables'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, () </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">=></span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> {</span></span>
|
||
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;"> // Load default branding CSS</span></span>
|
||
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;"> // ...</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;"> const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B;"> styles</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;"> =</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;"> getComputedStyle</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;">document</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">documentElement</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">)</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;"> const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B;"> missing</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B;"> string</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">[] </span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> []</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;"> for</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> (</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B;"> variable</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;"> of</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;"> requiredCssVariables</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">) {</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;"> const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B;"> value</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;"> styles</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;">getPropertyValue</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;">variable</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">).</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;">trim</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">()</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;"> if</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> (</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;">!</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;">value</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">) {</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;"> missing</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;">push</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;">variable</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">)</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> }</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> }</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;"> expect</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;">missing</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">).</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;">toEqual</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">([])</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> })</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">})</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><hr><h2 id="_4-icon-architektur" tabindex="-1"><a class="header-anchor" href="#_4-icon-architektur"><span>4. Icon-Architektur</span></a></h2><h3 id="entscheidung-hybrid-ansatz" tabindex="-1"><a class="header-anchor" href="#entscheidung-hybrid-ansatz"><span>Entscheidung: Hybrid-Ansatz</span></a></h3><p>Die Library verwendet eine <strong>Hybrid-Architektur</strong> für Icons:</p><div class="language- line-numbers-mode" data-highlighter="shiki" data-ext="" data-title="" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span>┌─────────────────────────────────────────────────────────────┐</span></span>
|
||
<span class="line"><span>│ @ocelot-social/ui (Library) │</span></span>
|
||
<span class="line"><span>├─────────────────────────────────────────────────────────────┤</span></span>
|
||
<span class="line"><span>│ src/components/OsIcon/icons/svgs/ # System-Icons │</span></span>
|
||
<span class="line"><span>│ ├── check.svg # Bestätigung, Checkboxen │</span></span>
|
||
<span class="line"><span>│ ├── close.svg # Modal, Chip, Dialoge │</span></span>
|
||
<span class="line"><span>│ └── plus.svg # Hinzufügen, Erstellen │</span></span>
|
||
<span class="line"><span>│ │</span></span>
|
||
<span class="line"><span>│ src/ocelot/icons/svgs/ # Ocelot-Icons │</span></span>
|
||
<span class="line"><span>│ └── angle-down.svg # Dropdown-Pfeil │</span></span>
|
||
<span class="line"><span>│ │</span></span>
|
||
<span class="line"><span>│ (Weitere System-Icons werden bei Bedarf ergänzt) │</span></span>
|
||
<span class="line"><span>└─────────────────────────────────────────────────────────────┘</span></span>
|
||
<span class="line"><span> ↓</span></span>
|
||
<span class="line"><span>┌─────────────────────────────────────────────────────────────┐</span></span>
|
||
<span class="line"><span>│ Webapp / Konsumierendes Projekt │</span></span>
|
||
<span class="line"><span>├─────────────────────────────────────────────────────────────┤</span></span>
|
||
<span class="line"><span>│ assets/icons/ # Feature-Icons (beliebig viele) │</span></span>
|
||
<span class="line"><span>│ ├── user.svg │</span></span>
|
||
<span class="line"><span>│ ├── bell.svg │</span></span>
|
||
<span class="line"><span>│ ├── heart.svg │</span></span>
|
||
<span class="line"><span>│ ├── settings.svg │</span></span>
|
||
<span class="line"><span>│ └── ... │</span></span>
|
||
<span class="line"><span>└─────────────────────────────────────────────────────────────┘</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="begrundung" tabindex="-1"><a class="header-anchor" href="#begrundung"><span>Begründung</span></a></h3><ol><li><strong>Library muss standalone funktionieren</strong> - Ein OsModal braucht einen Close-Button ohne zusätzliche Konfiguration</li><li><strong>616 Icons sind zu viel</strong> - Der Styleguide hat 616 Icons, die meisten werden nie gebraucht</li><li><strong>Feature-Icons gehören zur App</strong> - Icons wie <code>user</code>, <code>bell</code>, <code>heart</code> sind Business-Logik</li><li><strong>Branding-Flexibilität</strong> - Verschiedene Ocelot-Instanzen können unterschiedliche Icon-Sets verwenden</li></ol><h3 id="system-icons-in-library-enthalten-✅" tabindex="-1"><a class="header-anchor" href="#system-icons-in-library-enthalten-✅"><span>System-Icons (in Library enthalten) ✅</span></a></h3><table><thead><tr><th>Icon</th><th>Verwendung</th><th>Status</th></tr></thead><tbody><tr><td><code>check</code></td><td>Bestätigung, Checkboxen</td><td>✅ implementiert</td></tr><tr><td><code>close</code></td><td>Modal, Chip, Dialoge</td><td>✅ implementiert</td></tr><tr><td><code>plus</code></td><td>Hinzufügen, Erstellen</td><td>✅ implementiert</td></tr></tbody></table><p><strong>Geplant (bei Bedarf ergänzen):</strong></p><table><thead><tr><th>Icon</th><th>Verwendung in Komponenten</th></tr></thead><tbody><tr><td><code>chevron-down</code></td><td>OsSelect, OsDropdown, OsAccordion</td></tr><tr><td><code>chevron-up</code></td><td>OsSelect, OsAccordion</td></tr><tr><td><code>bars</code></td><td>OsPage (mobile menu)</td></tr><tr><td><code>search</code></td><td>OsInput (search variant)</td></tr></tbody></table><h3 id="api-design-implementiert" tabindex="-1"><a class="header-anchor" href="#api-design-implementiert"><span>API-Design (implementiert)</span></a></h3><div class="language-typescript line-numbers-mode" data-highlighter="shiki" data-ext="typescript" data-title="typescript" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;">// OsIcon — System-Icon per Name</span></span>
|
||
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;"><</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;">OsIcon</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;"> name</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">"close"</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;"> /></span></span>
|
||
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;"><</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;">OsIcon</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;"> name</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">"check"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;"> size</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">"lg"</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;"> /></span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;">// OsIcon — Custom Vue-Komponente (hat Vorrang vor name)</span></span>
|
||
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;"><</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;">OsIcon</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> :</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;">icon</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">"UserIcon"</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;"> /></span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;">// OsIcon — Semantic (mit aria-label)</span></span>
|
||
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;"><</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;">OsIcon</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;"> name</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">"close"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;"> aria</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;">-</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;">label</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">"Schließen"</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;"> /></span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;">// OsButton — Icon über #icon Slot (icon-system-agnostisch)</span></span>
|
||
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;"><</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;">OsButton</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;"> variant</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">"primary"</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;">></span></span>
|
||
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;"> <</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;">template</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> #</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;">icon</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;">></span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"><</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B;">OsIcon</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B;"> name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">"check"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> /></</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B;">template</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">></span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;"> Speichern</span></span>
|
||
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;"></</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;">OsButton</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;">></span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="svg-loading-vite-svg-icon-plugin" tabindex="-1"><a class="header-anchor" href="#svg-loading-vite-svg-icon-plugin"><span>SVG-Loading (vite-svg-icon Plugin)</span></a></h3><div class="language-typescript line-numbers-mode" data-highlighter="shiki" data-ext="typescript" data-title="typescript" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;">// SVGs werden via ?icon Query als Vue-Komponenten geladen</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">import</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> CheckIcon</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;"> from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> './check.svg?icon'</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;">// Plugin extrahiert viewBox + <path> und transformiert zu:</span></span>
|
||
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;">// h('svg', { viewBox, ... }, [h('path', { d })])</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="ocelot-icons-separates-entry-point" tabindex="-1"><a class="header-anchor" href="#ocelot-icons-separates-entry-point"><span>Ocelot-Icons (separates Entry-Point)</span></a></h3><div class="language-typescript line-numbers-mode" data-highlighter="shiki" data-ext="typescript" data-title="typescript" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;">// Dynamisches Loading via import.meta.glob</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">ocelotIcons</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> '@ocelot-social/ui/ocelot'</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;">// Filename → PascalCase: angle-down.svg → IconAngleDown</span></span>
|
||
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;">// Returns Record<string, () => VNode></span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="aktuelle-icon-statistik" tabindex="-1"><a class="header-anchor" href="#aktuelle-icon-statistik"><span>Aktuelle Icon-Statistik</span></a></h3><table><thead><tr><th>Quelle</th><th>Anzahl</th><th>Status</th></tr></thead><tbody><tr><td>Styleguide (_all)</td><td>616</td><td>Nicht übernehmen (FontAwesome 4 komplett)</td></tr><tr><td>Webapp (svgs)</td><td>238</td><td>Feature-Icons, bleiben in Webapp</td></tr><tr><td><strong>Library (system)</strong></td><td><strong>3</strong></td><td>✅ check, close, plus</td></tr><tr><td><strong>Ocelot-Icons</strong></td><td><strong>82</strong></td><td>✅ Feature-Icons + Kategorie-Icons (separates Entry-Point)</td></tr></tbody></table><hr><h2 id="_5-design-token-branding" tabindex="-1"><a class="header-anchor" href="#_5-design-token-branding"><span>5. Design-Token & Branding</span></a></h2><h3 id="_3-stufen-token-system" tabindex="-1"><a class="header-anchor" href="#_3-stufen-token-system"><span>3-Stufen Token-System</span></a></h3><div class="language- line-numbers-mode" data-highlighter="shiki" data-ext="" data-title="" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span>┌─────────────────────────────────────────────────────────┐</span></span>
|
||
<span class="line"><span>│ 1. BASE TOKENS (Rohwerte) │</span></span>
|
||
<span class="line"><span>│ --color-green: rgb(23, 181, 63); │</span></span>
|
||
<span class="line"><span>│ --color-teal: rgb(110, 139, 135); │</span></span>
|
||
<span class="line"><span>│ --space-small: 16px; │</span></span>
|
||
<span class="line"><span>└────────────────────────┬────────────────────────────────┘</span></span>
|
||
<span class="line"><span> ↓</span></span>
|
||
<span class="line"><span>┌─────────────────────────────────────────────────────────┐</span></span>
|
||
<span class="line"><span>│ 2. SEMANTIC TOKENS (Bedeutung) │</span></span>
|
||
<span class="line"><span>│ --color-primary: var(--color-green); │</span></span>
|
||
<span class="line"><span>│ --color-secondary: var(--color-teal); │</span></span>
|
||
<span class="line"><span>│ --text-color-base: var(--color-neutral-20); │</span></span>
|
||
<span class="line"><span>└────────────────────────┬────────────────────────────────┘</span></span>
|
||
<span class="line"><span> ↓</span></span>
|
||
<span class="line"><span>┌─────────────────────────────────────────────────────────┐</span></span>
|
||
<span class="line"><span>│ 3. COMPONENT TOKENS (Komponenten-spezifisch) │</span></span>
|
||
<span class="line"><span>│ --button-primary-bg: var(--color-primary); │</span></span>
|
||
<span class="line"><span>│ --button-primary-text: var(--color-primary-inverse);│</span></span>
|
||
<span class="line"><span>│ --card-bg: var(--background-color-base); │</span></span>
|
||
<span class="line"><span>└─────────────────────────────────────────────────────────┘</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="branding-flexibilitat" tabindex="-1"><a class="header-anchor" href="#branding-flexibilitat"><span>Branding-Flexibilität</span></a></h3><p>Jedes Branding kann auf jeder Ebene eingreifen:</p><div class="language-css line-numbers-mode" data-highlighter="shiki" data-ext="css" data-title="css" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;">/* Standard */</span></span>
|
||
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#56B6C2;">:root</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> {</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> --button-primary-bg</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;">var</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">--color-primary</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">);</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">}</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;">/* Yunite-Branding: Button nutzt Secondary statt Primary */</span></span>
|
||
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#56B6C2;">:root</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> {</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> --button-primary-bg</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;">var</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">--color-secondary</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">);</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">}</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="kompatibilitat-mit-bestehendem-system" tabindex="-1"><a class="header-anchor" href="#kompatibilitat-mit-bestehendem-system"><span>Kompatibilität mit bestehendem System</span></a></h3><ul><li>Bestehende SCSS-Variablen (<code>$color-primary</code>) → CSS Custom Properties (<code>--color-primary</code>)</li><li>Theo bleibt als Token-Quelle, generiert zusätzlich CSS Variables</li><li>Tailwind Theme nutzt CSS Variables: <code>bg-primary</code> → <code>var(--color-primary)</code></li></ul><h3 id="cva-tailwind-css-variablen" tabindex="-1"><a class="header-anchor" href="#cva-tailwind-css-variablen"><span>CVA + Tailwind + CSS-Variablen</span></a></h3><p><strong>Wie CVA funktioniert:</strong></p><p>CVA (Class Variance Authority) mappt Props typsicher auf Tailwind-Klassen:</p><div class="language-typescript line-numbers-mode" data-highlighter="shiki" data-ext="typescript" data-title="typescript" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;">// button.variants.ts</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">export</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;"> const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B;"> buttonVariants</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;"> =</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;"> cva</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span></span>
|
||
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> 'inline-flex items-center font-medium'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;">// Basis-Klassen</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> {</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> variants</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> {</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> variant</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> {</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> primary</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> 'bg-[var(--color-primary)] text-[var(--color-primary-contrast)]'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">,</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> danger</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> 'bg-[var(--color-danger)] text-[var(--color-danger-contrast)]'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">,</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> },</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> size</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> {</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> sm</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> 'h-8 px-3 text-sm'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">,</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> md</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> 'h-10 px-4 text-base'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">,</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> },</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> },</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> defaultVariants</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">variant</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> 'primary'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">size</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> 'md'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> },</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> }</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">)</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;">// Aufruf:</span></span>
|
||
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;">buttonVariants</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">({ </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">variant</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> 'primary'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">size</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> 'sm'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> })</span></span>
|
||
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;">// → 'inline-flex items-center font-medium bg-[var(--color-primary)] ... h-8 px-3 text-sm'</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>Was ist via Branding überschreibbar?</strong></p><table><thead><tr><th>Ebene</th><th>Überschreibbar</th><th>Beispiel</th></tr></thead><tbody><tr><td><strong>Farben</strong></td><td>✅ Ja (CSS-Variablen)</td><td><code>--color-primary</code>, <code>--color-danger</code></td></tr><tr><td><strong>Hover-Farben</strong></td><td>✅ Ja (CSS-Variablen)</td><td><code>--color-primary-hover</code></td></tr><tr><td><strong>Kontrast</strong></td><td>✅ Ja (CSS-Variablen)</td><td><code>--color-primary-contrast</code></td></tr><tr><td><strong>Größen</strong></td><td>❌ Nein (Tailwind-Skala)</td><td><code>h-10</code>, <code>px-4</code>, <code>text-base</code></td></tr><tr><td><strong>Abstände</strong></td><td>❌ Nein (Tailwind-Skala)</td><td><code>rounded-md</code>, <code>gap-2</code></td></tr><tr><td><strong>Einzelne Instanz</strong></td><td>✅ Ja (class-Prop)</td><td><code><OsButton class="h-12" /></code></td></tr></tbody></table><p><strong>Branding-Beispiel:</strong></p><div class="language-css line-numbers-mode" data-highlighter="shiki" data-ext="css" data-title="css" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;">/* branding/default.css */</span></span>
|
||
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#56B6C2;">:root</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> {</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> --color-primary</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;">rgb</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66;">23</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66;">181</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66;">63</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">);</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> --color-primary-contrast</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;">rgb</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66;">255</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66;">255</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66;">255</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">);</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> --color-primary-hover</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;">rgb</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66;">18</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66;">140</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66;">49</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">);</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">}</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;">/* branding/yunite.css (überschreibt) */</span></span>
|
||
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#56B6C2;">:root</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> {</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> --color-primary</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;">rgb</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66;">110</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66;">139</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66;">135</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">);</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> --color-primary-contrast</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;">rgb</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66;">255</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66;">255</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66;">255</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">);</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> --color-primary-hover</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;">rgb</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66;">90</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66;">115</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66;">112</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">);</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">}</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>Die Rolle von cn() (clsx + tailwind-merge):</strong></p><div class="language-typescript line-numbers-mode" data-highlighter="shiki" data-ext="typescript" data-title="typescript" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">clsx</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> 'clsx'</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">twMerge</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> 'tailwind-merge'</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">export</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;"> function</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;"> cn</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">...</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic;">inputs</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">) {</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;"> return</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;"> twMerge</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;">clsx</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;">inputs</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">))</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">}</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;">// Beispiel: Custom class überschreibt CVA-Werte</span></span>
|
||
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;">cn</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">'h-10 px-4'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">'h-12 px-8'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">) </span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;">// → 'h-12 px-8' (letzte gewinnt)</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>Architektur-Übersicht:</strong></p><div class="language- line-numbers-mode" data-highlighter="shiki" data-ext="" data-title="" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span>┌─────────────────────────────────────────────────────────────┐</span></span>
|
||
<span class="line"><span>│ CSS-Variablen (Branding-überschreibbar) │</span></span>
|
||
<span class="line"><span>│ --color-primary, --color-danger, --color-*-hover, etc. │</span></span>
|
||
<span class="line"><span>└─────────────────────────────────────────────────────────────┘</span></span>
|
||
<span class="line"><span> ↓</span></span>
|
||
<span class="line"><span>┌─────────────────────────────────────────────────────────────┐</span></span>
|
||
<span class="line"><span>│ CVA-Varianten (nutzen CSS-Variablen) │</span></span>
|
||
<span class="line"><span>│ bg-[var(--color-primary)], text-[var(--color-contrast)] │</span></span>
|
||
<span class="line"><span>└─────────────────────────────────────────────────────────────┘</span></span>
|
||
<span class="line"><span> ↓</span></span>
|
||
<span class="line"><span>┌─────────────────────────────────────────────────────────────┐</span></span>
|
||
<span class="line"><span>│ Tailwind-Klassen (feste Skala, konsistent) │</span></span>
|
||
<span class="line"><span>│ h-8, h-10, h-12, px-3, px-4, rounded-md, text-sm │</span></span>
|
||
<span class="line"><span>└─────────────────────────────────────────────────────────────┘</span></span>
|
||
<span class="line"><span> ↓</span></span>
|
||
<span class="line"><span>┌─────────────────────────────────────────────────────────────┐</span></span>
|
||
<span class="line"><span>│ cn() + class-Prop (Escape-Hatch für Einzelfälle) │</span></span>
|
||
<span class="line"><span>│ <OsButton class="h-20 rounded-full" /> │</span></span>
|
||
<span class="line"><span>└─────────────────────────────────────────────────────────────┘</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>Designentscheidung:</strong></p><ul><li>Farben sind flexibel (Branding via CSS-Variablen)</li><li>Größen sind konsistent (Tailwind-Skala, nicht überschreibbar)</li><li><code>class</code>-Prop erlaubt Ausnahmen für Einzelfälle</li></ul><hr><h1 id="prozesse-qualitat" tabindex="-1"><a class="header-anchor" href="#prozesse-qualitat"><span>PROZESSE & QUALITÄT</span></a></h1><h2 id="_6-ci-cd-release" tabindex="-1"><a class="header-anchor" href="#_6-ci-cd-release"><span>6. CI/CD & Release</span></a></h2><h3 id="release-workflow-release-please" tabindex="-1"><a class="header-anchor" href="#release-workflow-release-please"><span>Release-Workflow (release-please)</span></a></h3><p><strong>Manifest-Modus für Monorepo:</strong></p><ul><li>Nur Commits in <code>packages/ui/</code> werden betrachtet</li><li>Eigene Versionierung unabhängig vom Hauptrepo</li><li>Automatischer Changelog nur für dieses Paket</li></ul><div class="language- line-numbers-mode" data-highlighter="shiki" data-ext="" data-title="" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span>┌─────────────────────────────────────────────────────────────────┐</span></span>
|
||
<span class="line"><span>│ RELEASE WORKFLOW │</span></span>
|
||
<span class="line"><span>├─────────────────────────────────────────────────────────────────┤</span></span>
|
||
<span class="line"><span>│ 1. Commit mit Conventional Commits Format │</span></span>
|
||
<span class="line"><span>│ 2. release-please erstellt Release-PR │</span></span>
|
||
<span class="line"><span>│ 3. PR merge → Version bump + Changelog │</span></span>
|
||
<span class="line"><span>│ 4. Automatisch: npm publish + Storybook deploy │</span></span>
|
||
<span class="line"><span>└─────────────────────────────────────────────────────────────────┘</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>Konfigurationsdateien:</strong></p><ul><li><code>.release-please-manifest.json</code> - Versionen</li><li><code>release-please-config.json</code> - Paket-Konfiguration</li></ul><h3 id="github-workflows" tabindex="-1"><a class="header-anchor" href="#github-workflows"><span>GitHub Workflows</span></a></h3><table><thead><tr><th>Workflow</th><th>Trigger</th><th>Beschreibung</th></tr></thead><tbody><tr><td><strong>Lint</strong></td><td>Push/PR</td><td>eslint-config-it4c (TypeScript + Vue + Prettier)</td></tr><tr><td><strong>Test</strong></td><td>Push/PR</td><td>Vitest Unit-Tests</td></tr><tr><td><strong>Build</strong></td><td>Push/PR</td><td>Vite Build verifizieren</td></tr><tr><td><strong>Release</strong></td><td>Push to main</td><td>release-please PR erstellen</td></tr><tr><td><strong>Publish</strong></td><td>Release created</td><td>npm publish + Storybook deploy</td></tr></tbody></table><h3 id="qualitatssicherung-bei-prs" tabindex="-1"><a class="header-anchor" href="#qualitatssicherung-bei-prs"><span>Qualitätssicherung bei PRs</span></a></h3><div class="language- line-numbers-mode" data-highlighter="shiki" data-ext="" data-title="" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span>PR erstellt</span></span>
|
||
<span class="line"><span> ↓</span></span>
|
||
<span class="line"><span>┌─────────────┐</span></span>
|
||
<span class="line"><span>│ Lint │──→ eslint-config-it4c (TS + Vue + Prettier)</span></span>
|
||
<span class="line"><span>├─────────────┤</span></span>
|
||
<span class="line"><span>│ Tests │──→ Vitest</span></span>
|
||
<span class="line"><span>├─────────────┤</span></span>
|
||
<span class="line"><span>│ Build │──→ Vite</span></span>
|
||
<span class="line"><span>└─────────────┘</span></span>
|
||
<span class="line"><span> ↓</span></span>
|
||
<span class="line"><span>Alle grün → Merge erlaubt</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="automatische-deployments" tabindex="-1"><a class="header-anchor" href="#automatische-deployments"><span>Automatische Deployments</span></a></h3><table><thead><tr><th>Event</th><th>Aktion</th></tr></thead><tbody><tr><td>Release erstellt</td><td>npm publish</td></tr><tr><td>Release erstellt</td><td>Storybook build + deploy auf Server</td></tr></tbody></table><p><strong>Storybook Deploy (Webhook):</strong></p><ol><li>GitHub sendet Webhook bei Release-Event</li><li>Server empfängt Webhook</li><li>Server führt <code>scripts/deploy-storybook.sh</code> aus (Teil des Repos)</li><li>Script: git pull → npm ci → storybook build → copy to webroot</li></ol><h3 id="github-workflows-vollstandige-liste" tabindex="-1"><a class="header-anchor" href="#github-workflows-vollstandige-liste"><span>GitHub Workflows (vollständige Liste)</span></a></h3><table><thead><tr><th>Workflow</th><th>Trigger</th><th>Tool</th><th>Beschreibung</th></tr></thead><tbody><tr><td><strong>lint</strong></td><td>Push/PR</td><td>eslint-config-it4c</td><td>Code-Stil prüfen</td></tr><tr><td><strong>typecheck</strong></td><td>Push/PR</td><td>TypeScript</td><td>Typ-Fehler finden</td></tr><tr><td><strong>test</strong></td><td>Push/PR</td><td>Vitest</td><td>Unit-Tests</td></tr><tr><td><strong>test-a11y</strong></td><td>Push/PR</td><td>axe-core</td><td>Accessibility-Tests</td></tr><tr><td><strong>test-visual</strong></td><td>Push/PR</td><td>Playwright</td><td>Visual Regression Screenshots</td></tr><tr><td><strong>build</strong></td><td>Push/PR</td><td>Vite</td><td>Build verifizieren</td></tr><tr><td><strong>build-storybook</strong></td><td>Push/PR</td><td>Storybook</td><td>Dokumentation bauen</td></tr><tr><td><strong>size-check</strong></td><td>Push/PR</td><td>size-limit</td><td>Bundle-Größe prüfen</td></tr><tr><td><strong>release</strong></td><td>Push main</td><td>release-please</td><td>Release-PR erstellen</td></tr><tr><td><strong>publish</strong></td><td>Release</td><td>npm</td><td>Auf npm veröffentlichen</td></tr><tr><td><strong>deploy-docs</strong></td><td>Release</td><td>Webhook</td><td>Storybook auf Server deployen</td></tr><tr><td><strong>check-ui-release</strong></td><td>Ocelot Release</td><td>Git</td><td>Prüft unreleased UI-Änderungen</td></tr></tbody></table><h3 id="erweiterte-qualitatssicherung" tabindex="-1"><a class="header-anchor" href="#erweiterte-qualitatssicherung"><span>Erweiterte Qualitätssicherung</span></a></h3><table><thead><tr><th>Maßnahme</th><th>Tool</th><th>Beschreibung</th></tr></thead><tbody><tr><td><strong>Visual Regression</strong></td><td>Playwright</td><td>Screenshot-Vergleiche bei Änderungen</td></tr><tr><td><strong>Accessibility</strong></td><td>@axe-core/playwright</td><td>A11y-Prüfung integriert in Visual Tests</td></tr><tr><td><strong>Bundle Size</strong></td><td>size-limit</td><td>Warnung wenn Library-Größe Schwellwert überschreitet</td></tr></tbody></table><h3 id="migrations-absicherung" tabindex="-1"><a class="header-anchor" href="#migrations-absicherung"><span>Migrations-Absicherung</span></a></h3><p><strong>Feature Parity Checklist</strong> (pro Komponente):</p><div class="language- line-numbers-mode" data-highlighter="shiki" data-ext="" data-title="" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span>┌─────────────────────────────────────────────────────────┐</span></span>
|
||
<span class="line"><span>│ KOMPONENTE: [Name] │</span></span>
|
||
<span class="line"><span>├─────────────────────────────────────────────────────────┤</span></span>
|
||
<span class="line"><span>│ [ ] Alle Props der alten Komponente übernommen │</span></span>
|
||
<span class="line"><span>│ [ ] Alle Events identisch │</span></span>
|
||
<span class="line"><span>│ [ ] Alle Slots vorhanden │</span></span>
|
||
<span class="line"><span>│ [ ] Default-Werte identisch │</span></span>
|
||
<span class="line"><span>│ [ ] Visuell identisch (Screenshot-Vergleich) │</span></span>
|
||
<span class="line"><span>│ [ ] A11y mindestens gleich gut │</span></span>
|
||
<span class="line"><span>│ [ ] Dokumentation vollständig │</span></span>
|
||
<span class="line"><span>└─────────────────────────────────────────────────────────┘</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>Deprecation Warnings:</strong> Nach erfolgreicher Migration einer Komponente werden in der Vue 2 Webapp Warnungen eingebaut:</p><div class="language-js line-numbers-mode" data-highlighter="shiki" data-ext="js" data-title="js" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;">// In alter Komponente</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">if</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> (</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;">process</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B;">env</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">.</span><span style="--shiki-light:#986801;--shiki-dark:#E06C75;">NODE_ENV</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;"> ===</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> 'development'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">) {</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;"> console</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;">warn</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">'[DEPRECATED] Button: Bitte @ocelot-social/ui verwenden'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">)</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">}</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><hr><h2 id="_7-dokumentation-dx" tabindex="-1"><a class="header-anchor" href="#_7-dokumentation-dx"><span>7. Dokumentation & DX</span></a></h2><h3 id="storybook-als-komponenten-dokumentation" tabindex="-1"><a class="header-anchor" href="#storybook-als-komponenten-dokumentation"><span>Storybook als Komponenten-Dokumentation</span></a></h3><p>Die Komponenten werden über Storybook dokumentiert und auf einer öffentlichen Webseite für Entwickler zugänglich gemacht.</p><p><strong>Features für Entwickler:</strong></p><ul><li>Interaktive Playgrounds zum Ausprobieren</li><li>Alle Varianten und Zustände auf einen Blick</li><li>Copy-paste-fertige Code-Snippets</li><li>Suchfunktion</li><li>Props-Dokumentation mit Typen und Defaults</li></ul><p><strong>Hosting:</strong></p><ul><li>Eigener Server (öffentlich zugänglich)</li><li>Static Build via <code>storybook build</code></li><li>Deployment bei jedem Release</li></ul><p><strong>Workflow:</strong></p><div class="language- line-numbers-mode" data-highlighter="shiki" data-ext="" data-title="" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span>Komponente entwickeln → Storybook Story schreiben → Build → Deploy auf Server</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div></div></div><hr><h2 id="_8-migrationsstrategie" tabindex="-1"><a class="header-anchor" href="#_8-migrationsstrategie"><span>8. Migrationsstrategie</span></a></h2><h3 id="grundprinzipien" tabindex="-1"><a class="header-anchor" href="#grundprinzipien"><span>Grundprinzipien</span></a></h3><div class="language- line-numbers-mode" data-highlighter="shiki" data-ext="" data-title="" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span>┌─────────────────────────────────────────────────────────────────┐</span></span>
|
||
<span class="line"><span>│ MIGRATIONSSTRATEGIE │</span></span>
|
||
<span class="line"><span>├─────────────────────────────────────────────────────────────────┤</span></span>
|
||
<span class="line"><span>│ 1. Library vollständig in Vue 3 schreiben │</span></span>
|
||
<span class="line"><span>│ 2. vue-demi für Vue 2 Kompatibilität │</span></span>
|
||
<span class="line"><span>│ 3. Komponente für Komponente migrieren │</span></span>
|
||
<span class="line"><span>│ 4. Jede Komponente: vollständig getestet + in Storybook │</span></span>
|
||
<span class="line"><span>│ 5. Nach Migration: Duplikate entfernen, Varianten konsolidieren│</span></span>
|
||
<span class="line"><span>└─────────────────────────────────────────────────────────────────┘</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="ablauf-pro-komponente" tabindex="-1"><a class="header-anchor" href="#ablauf-pro-komponente"><span>Ablauf pro Komponente</span></a></h3><div class="language- line-numbers-mode" data-highlighter="shiki" data-ext="" data-title="" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span>┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐</span></span>
|
||
<span class="line"><span>│ ANALYSE │ → │ SPEC │ → │ DEVELOP │ → │ QA │ → │ INTEGRATE │</span></span>
|
||
<span class="line"><span>├──────────────┤ ├──────────────┤ ├──────────────┤ ├──────────────┤ ├──────────────┤</span></span>
|
||
<span class="line"><span>│ Bestehende │ │ Props │ │ Vue 3 Code │ │ Alle Tests │ │ In Vue 2 │</span></span>
|
||
<span class="line"><span>│ Varianten │ │ Varianten │ │ Unit Tests │ │ grün │ │ Projekt │</span></span>
|
||
<span class="line"><span>│ identifiz. │ │ Zustände │ │ Storybook │ │ Visual Regr. │ │ einbinden │</span></span>
|
||
<span class="line"><span>│ Duplikate │ │ A11y │ │ Stories │ │ A11y Check │ │ │</span></span>
|
||
<span class="line"><span>│ finden │ │ Tokens │ │ │ │ Review │ │ Alte Komp. │</span></span>
|
||
<span class="line"><span>│ │ │ │ │ │ │ │ │ entfernen │</span></span>
|
||
<span class="line"><span>└──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="komponenten-protokoll" tabindex="-1"><a class="header-anchor" href="#komponenten-protokoll"><span>Komponenten-Protokoll</span></a></h3><p>Pro Komponente wird eine Status-Datei <strong>im Komponenten-Ordner</strong> geführt (Colocation):</p><div class="language- line-numbers-mode" data-highlighter="shiki" data-ext="" data-title="" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span>packages/ui/src/components/OsButton/</span></span>
|
||
<span class="line"><span>├── OsButton.vue</span></span>
|
||
<span class="line"><span>├── OsButton.spec.ts</span></span>
|
||
<span class="line"><span>├── button.variants.ts</span></span>
|
||
<span class="line"><span>├── index.ts</span></span>
|
||
<span class="line"><span>└── STATUS.md ← Status-Datei hier</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>Inhalt:</strong></p><ul><li>Übersicht (Implementierungsstatus, Tests, A11y)</li><li>Implementierte Features (Props, Slots, Events)</li><li>Fehlende Features (aus KATALOG.md)</li><li>Test-Coverage</li><li>Architektur (Datei-Struktur, CVA-Pattern)</li><li>Nächste Schritte</li><li>Changelog</li></ul><p><strong>Begründung für Colocation:</strong></p><ul><li>Status-Datei direkt beim Code auffindbar</li><li>Einfacher zu aktualisieren bei Änderungen</li><li>Kein separater docs/ Ordner notwendig</li><li>Folgt dem Prinzip "Code und Dokumentation zusammen"</li></ul><h3 id="qualitatsanforderungen-pro-komponente" tabindex="-1"><a class="header-anchor" href="#qualitatsanforderungen-pro-komponente"><span>Qualitätsanforderungen pro Komponente</span></a></h3><table><thead><tr><th>Anforderung</th><th>Beschreibung</th></tr></thead><tbody><tr><td><strong>Vollständig getestet</strong></td><td>Unit-Tests für alle Props, Varianten, Edge-Cases</td></tr><tr><td><strong>Storybook Stories</strong></td><td>Alle Varianten und Zustände dokumentiert</td></tr><tr><td><strong>Detaillierte Spec</strong></td><td>Props, Events, Slots, A11y vor Implementierung definiert</td></tr><tr><td><strong>Token-basiert</strong></td><td>Nutzt ausschließlich Design Tokens, keine hardcoded Werte</td></tr><tr><td><strong>Vue 2 kompatibel</strong></td><td>Funktioniert via vue-demi im bestehenden Projekt</td></tr><tr><td><strong>A11y geprüft</strong></td><td>axe-core Tests bestanden</td></tr><tr><td><strong>Visual Regression</strong></td><td>Screenshot-Baseline erstellt</td></tr><tr><td><strong>Feature Parity</strong></td><td>Checkliste abgehakt (alle Props, Events, Slots)</td></tr></tbody></table><h3 id="konsolidierungsziele" tabindex="-1"><a class="header-anchor" href="#konsolidierungsziele"><span>Konsolidierungsziele</span></a></h3><p>Bei der Migration werden:</p><ul><li><strong>Duplikate eliminiert</strong> - nur eine kanonische Version pro Komponente</li><li><strong>Unnötige Varianten entfernt</strong> - nur tatsächlich genutzte Varianten</li><li><strong>Zusammenführbare Komponenten vereint</strong> - z.B. verschiedene Button-Typen → ein Button mit Props</li><li><strong>Inkonsistenzen bereinigt</strong> - einheitliche API, Naming, Patterns</li></ul><hr><h2 id="_9-dokumentationsstrategie" tabindex="-1"><a class="header-anchor" href="#_9-dokumentationsstrategie"><span>9. Dokumentationsstrategie</span></a></h2><blockquote><p>Siehe ausführliche Version in §15 Dokumentationsstrategie (Details). Kurzzusammenfassung: Generierte Docs (vue-component-meta) + Manuell (Storybook).</p></blockquote><hr><h2 id="_10-kompatibilitatstests" tabindex="-1"><a class="header-anchor" href="#_10-kompatibilitatstests"><span>10. Kompatibilitätstests</span></a></h2><blockquote><p>Vue 2/3 × Tailwind/CSS Testmatrix. Details siehe §18.</p></blockquote><p><strong>Kurzfassung:</strong></p><ul><li>4 Example Apps: vue3-tailwind, vue3-css, vue2-tailwind, vue2-css</li><li>Vitest Unit-Tests mit Vue 2.7 und Vue 3.4</li><li>Playwright E2E für alle 4 Kombinationen</li><li>Package-Validierung: publint, arethetypeswrong</li></ul><hr><h1 id="referenz-historie" tabindex="-1"><a class="header-anchor" href="#referenz-historie"><span>REFERENZ & HISTORIE</span></a></h1><h2 id="_11-entscheidungen" tabindex="-1"><a class="header-anchor" href="#_11-entscheidungen"><span>11. Entscheidungen</span></a></h2><blockquote><p>73 Entscheidungen in 9 Kategorien</p></blockquote><h3 id="vision-ziele" tabindex="-1"><a class="header-anchor" href="#vision-ziele"><span>Vision & Ziele</span></a></h3><table><thead><tr><th>#</th><th>Frage</th><th>Entscheidung</th><th>Begründung</th></tr></thead><tbody><tr><td>1</td><td>Hauptziel</td><td>Migration vorbereiten</td><td>Schrittweise Vue 2 → Vue 3</td></tr></tbody></table><h3 id="tech-stack" tabindex="-1"><a class="header-anchor" href="#tech-stack"><span>Tech-Stack</span></a></h3><table><thead><tr><th>#</th><th>Frage</th><th>Entscheidung</th><th>Begründung</th></tr></thead><tbody><tr><td>2</td><td>Framework</td><td>Vue 3 + Vite</td><td>Modern, schnell, gute DX</td></tr><tr><td>3</td><td>Dokumentation</td><td>Storybook 10</td><td>Storybook 1.0-beta hängt mit Tailwind v4 + Node 25, Storybook funktioniert sofort</td></tr><tr><td>4</td><td>Styling</td><td>Tailwind + CSS Variables</td><td>Modern + Branding-kompatibel</td></tr><tr><td>6</td><td>Testing</td><td>Vitest</td><td>Vite-nativ, Jest-kompatibel</td></tr><tr><td>9</td><td>Vue 2 Kompatibilität</td><td>vue-demi</td><td>Library funktioniert in beiden Vue-Versionen</td></tr><tr><td>16</td><td>Linting</td><td>eslint-config-it4c</td><td>TypeScript + Vue + Prettier + weitere Regeln</td></tr><tr><td>34</td><td>Vue API</td><td><code><script setup></code></td><td>Composition API mit script setup</td></tr><tr><td>36</td><td>Package Manager</td><td>npm</td><td>Bereits im Projekt verwendet</td></tr><tr><td>41</td><td>TypeScript</td><td>strict: true</td><td>Strikte Typisierung</td></tr><tr><td>43</td><td>Vue 2 Minimum</td><td>Vue 2.7</td><td>Erforderlich für <code><script setup></code> Support</td></tr><tr><td>69</td><td>Varianten-System</td><td>CVA (class-variance-authority)</td><td>Typsichere Props, Composable, DX wie shadcn/ui</td></tr><tr><td>70</td><td>Klassen-Utility</td><td>cn() (clsx + tailwind-merge)</td><td>Bedingte Klassen + Tailwind-Deduplizierung</td></tr><tr><td>71</td><td>Komponenten-Status</td><td>STATUS.md im Komponenten-Ordner</td><td>Colocated mit Code, nicht in separatem docs/</td></tr></tbody></table><h3 id="build-distribution" tabindex="-1"><a class="header-anchor" href="#build-distribution"><span>Build & Distribution</span></a></h3><table><thead><tr><th>#</th><th>Frage</th><th>Entscheidung</th><th>Begründung</th></tr></thead><tbody><tr><td>8</td><td>Paket-Format</td><td>npm Library</td><td>Wiederverwendbar</td></tr><tr><td>25</td><td>Paket-Name</td><td>@ocelot-social/ui</td><td>Unter ocelot-social npm Org</td></tr><tr><td>31</td><td>Build-Strategie</td><td>Dual-Build</td><td>Tailwind Preset + Vorkompilierte CSS</td></tr><tr><td>32</td><td>Lizenz</td><td>Apache 2.0</td><td>Permissiv mit Patent-Schutz</td></tr><tr><td>33</td><td>Repository</td><td>Monorepo</td><td>Ocelot-Social/packages/ui/</td></tr><tr><td>42</td><td>Ordnerstruktur</td><td>packages/ui</td><td>Monorepo-Standard, erweiterbar</td></tr><tr><td>44</td><td>Dev-Linking</td><td>Nuxt Alias</td><td>LOCAL_UI=true für lokale Library</td></tr></tbody></table><h3 id="ci-cd-release" tabindex="-1"><a class="header-anchor" href="#ci-cd-release"><span>CI/CD & Release</span></a></h3><table><thead><tr><th>#</th><th>Frage</th><th>Entscheidung</th><th>Begründung</th></tr></thead><tbody><tr><td>15</td><td>Release-Tool</td><td>release-please (Manifest)</td><td>Monorepo-kompatibel, nur packages/ui Änderungen</td></tr><tr><td>17</td><td>CI Workflows</td><td>Lint, Test, Build</td><td>Qualitätssicherung bei jedem PR</td></tr><tr><td>18</td><td>npm Publish</td><td>Automatisch bei Release</td><td>Nach release-please PR merge</td></tr><tr><td>19</td><td>Doku-Deploy</td><td>Automatisch bei Release</td><td>storybook build + deploy</td></tr><tr><td>45</td><td>Release-Check</td><td>Git-basiert</td><td>Prüft unreleased UI-Änderungen vor Ocelot-Release</td></tr><tr><td>46</td><td>Storybook Deploy</td><td>Webhook + Script</td><td>Server zieht und baut bei Release</td></tr><tr><td>50</td><td>GitHub Workflows</td><td>12 Workflows</td><td>Vollständige CI/CD Pipeline</td></tr></tbody></table><h3 id="testing-qualitat" tabindex="-1"><a class="header-anchor" href="#testing-qualitat"><span>Testing & Qualität</span></a></h3><table><thead><tr><th>#</th><th>Frage</th><th>Entscheidung</th><th>Begründung</th></tr></thead><tbody><tr><td>20</td><td>Visual Regression</td><td>Playwright Screenshots</td><td>Visueller Vergleich bei Änderungen</td></tr><tr><td>21</td><td>Accessibility</td><td>@axe-core/playwright</td><td>A11y integriert in Visual Tests</td></tr><tr><td>22</td><td>Bundle Size</td><td>size-limit</td><td>Warnung bei Größenüberschreitung</td></tr><tr><td>23</td><td>Feature Parity</td><td>Checkliste pro Komponente</td><td>Sicherstellen dass alle Features migriert</td></tr><tr><td>37</td><td>Test Coverage</td><td>100%</td><td>Vollständige Testabdeckung</td></tr><tr><td>57</td><td>Kompatibilitätstests</td><td>4er-Matrix + CI</td><td>Vue 2/3 × Tailwind/CSS (siehe §18)</td></tr></tbody></table><h3 id="design-system-branding" tabindex="-1"><a class="header-anchor" href="#design-system-branding"><span>Design-System & Branding</span></a></h3><table><thead><tr><th>#</th><th>Frage</th><th>Entscheidung</th><th>Begründung</th></tr></thead><tbody><tr><td>5</td><td>Token-System</td><td>3-Stufen (Base/Semantic/Component)</td><td>Maximale Branding-Flexibilität</td></tr><tr><td>29</td><td>Dark Mode</td><td>Ja, von Anfang an</td><td>Alle Komponenten mit Light/Dark</td></tr><tr><td>30</td><td>Icons</td><td>Hybrid-Architektur</td><td>System-Icons in Library, Feature-Icons in App</td></tr><tr><td>51</td><td>Icon-Architektur</td><td>Hybrid</td><td>~10 System-Icons in Library, Rest in App (siehe §4)</td></tr><tr><td>59</td><td>Size Props</td><td>Tailwind-Skala (sm, md, lg, xl)</td><td>Komponentenspezifisch, OsButton nutzt sm-xl</td></tr><tr><td>60</td><td>Rounded Props</td><td>Tailwind-Skala (none, sm, md, lg, xl, 2xl, 3xl, full)</td><td>Konsistenz mit Tailwind border-radius</td></tr><tr><td>61</td><td>Shadow Props</td><td>Tailwind-Skala (none, sm, md, lg, xl, 2xl)</td><td>Konsistenz mit Tailwind box-shadow</td></tr><tr><td>62</td><td>Variant Props</td><td>Semantisch (primary, secondary, danger, warning, success, info)</td><td>Übliche UI-Farbvarianten</td></tr><tr><td>63</td><td>Dark Mode Handling</td><td>CSS-Klassen (<code>dark:</code> Prefix)</td><td>Standard-Tailwind-Pattern, keine "inverse" Props</td></tr><tr><td>64</td><td>Prop-Vollständigkeit</td><td>Alle Werte einer Skala</td><td>Konsistente API, keine Teilmengen pro Komponente</td></tr><tr><td>65</td><td>CSS Variable Defaults</td><td>Keine Defaults in Library</td><td>Webapp definiert Branding, Library ist design-agnostisch</td></tr><tr><td>66</td><td>Branding-Hierarchie</td><td>Webapp → Spezialisiertes Branding</td><td>Default-Branding in Webapp, Overrides pro Instanz</td></tr><tr><td>67</td><td>Variable-Validierung</td><td>Runtime-Check in Development</td><td><code>validateCssVariables()</code> warnt bei fehlenden Variablen</td></tr><tr><td>68</td><td>Branding-Test (Webapp)</td><td>CI-Test in Webapp</td><td>Webapp testet, dass Default-Branding alle Library-Variablen definiert</td></tr><tr><td>72</td><td>Webapp ↔ Maintenance Sharing</td><td>Webapp als Source of Truth</td><td>Kein separates "shared" Package, maintenance importiert aus webapp/ (siehe §16a)</td></tr><tr><td>73</td><td>Daten-Entkopplung</td><td>ViewModel/Mapper Pattern</td><td>Komponenten kennen nur ViewModels, Mapper transformieren API-Daten (siehe §16b)</td></tr></tbody></table><h3 id="komponenten-api-konventionen" tabindex="-1"><a class="header-anchor" href="#komponenten-api-konventionen"><span>Komponenten-API & Konventionen</span></a></h3><table><thead><tr><th>#</th><th>Frage</th><th>Entscheidung</th><th>Begründung</th></tr></thead><tbody><tr><td>26</td><td>Komponenten-Prefix</td><td>Os</td><td>OsButton, OsCard, etc.</td></tr><tr><td>27</td><td>Browser-Support</td><td>Modern only</td><td>Letzte 2 Versionen von Chrome, Firefox, Safari, Edge</td></tr><tr><td>28</td><td>SSR</td><td>Ja</td><td>Nuxt-kompatibel</td></tr><tr><td>35</td><td>Sprache</td><td>Englisch</td><td>Code, Comments, Docs</td></tr><tr><td>38</td><td>Dateinamen</td><td>PascalCase</td><td>OsButton.vue, OsCard.vue</td></tr><tr><td>39</td><td>i18n</td><td>Nur Props</td><td>Keine Default-Texte in Komponenten</td></tr><tr><td>40</td><td>Breakpoints</td><td>Tailwind Standard</td><td>sm, md, lg, xl, 2xl</td></tr><tr><td>55</td><td>Komponenten-Abgrenzung</td><td>Entscheidungsbaum</td><td>Library: präsentational, Webapp: Business-Logik (siehe §16)</td></tr></tbody></table><h3 id="dokumentation" tabindex="-1"><a class="header-anchor" href="#dokumentation"><span>Dokumentation</span></a></h3><table><thead><tr><th>#</th><th>Frage</th><th>Entscheidung</th><th>Begründung</th></tr></thead><tbody><tr><td>13</td><td>Doku-Hosting</td><td>Eigener Server</td><td>Öffentlich zugängliche Komponenten-Doku</td></tr><tr><td>14</td><td>Doku-Zugang</td><td>Öffentlich</td><td>Für alle Entwickler frei zugänglich</td></tr><tr><td>47</td><td>Komponenten-Protokoll</td><td>STATUS.md im Komponenten-Ordner</td><td>Colocated: src/components/OsButton/STATUS.md</td></tr><tr><td>52</td><td>Docs-Generierung</td><td>vue-component-meta</td><td>Komponenten-Tabelle aus Code generiert</td></tr><tr><td>53</td><td>Docs CI-Check</td><td>GitHub Workflow</td><td>Prüft JSDoc-Coverage und README-Aktualität</td></tr><tr><td>54</td><td>Nach Migration</td><td>ARCHITECTURE.md</td><td>PROJEKT.md → ARCHITECTURE.md, KATALOG.md archivieren</td></tr></tbody></table><h3 id="migration-prozess" tabindex="-1"><a class="header-anchor" href="#migration-prozess"><span>Migration & Prozess</span></a></h3><table><thead><tr><th>#</th><th>Frage</th><th>Entscheidung</th><th>Begründung</th></tr></thead><tbody><tr><td>7</td><td>Komponenten-Reihenfolge</td><td>Nach Bedarf</td><td>Flexibel, Token-System zuerst</td></tr><tr><td>10</td><td>Migrations-Ablauf</td><td>Komponente für Komponente</td><td>Kontrolliert, schrittweise</td></tr><tr><td>11</td><td>Vor-Analyse</td><td>Vollständige Analyse</td><td>Duplikate/Probleme vor Migration identifizieren</td></tr><tr><td>12</td><td>Spezifikation</td><td>Detailliert vor Implementierung</td><td>Props, Varianten, A11y vorher definieren</td></tr><tr><td>24</td><td>Deprecation Warnings</td><td>Console Warnings</td><td>Hinweise in alter Codebase</td></tr><tr><td>48</td><td>Katalogisierung</td><td>KATALOG.md</td><td>Unterbrechbar, Webapp + Styleguide</td></tr><tr><td>49</td><td>Fortschritt</td><td>Berechenbar</td><td>Pro Phase und Gesamt</td></tr><tr><td>56</td><td>Externe Abhängigkeiten</td><td>✅ Gelöst</td><td>eslint-config-it4c v0.8.0 ist modular</td></tr><tr><td>58</td><td>Komplexitätsanalyse</td><td>Dokumentiert in §19</td><td>Risikofaktoren, Parallelisierbarkeit, Aufwandstreiber</td></tr></tbody></table><hr><h2 id="_12-arbeitsprotokoll" tabindex="-1"><a class="header-anchor" href="#_12-arbeitsprotokoll"><span>12. Arbeitsprotokoll</span></a></h2><table><thead><tr><th>Datum</th><th>Beschreibung</th><th>Ergebnis</th></tr></thead><tbody><tr><td>2026-02-04</td><td>Projektstart</td><td>Ordner und Planungsdokument erstellt</td></tr><tr><td>2026-02-04</td><td>Tech-Stack Entscheidungen</td><td>Alle Kernentscheidungen getroffen</td></tr><tr><td>2026-02-04</td><td>Migrationsstrategie</td><td>vue-demi, Komponente-für-Komponente, Analyse-First</td></tr><tr><td>2026-02-04</td><td>Dokumentation</td><td>Komponenten-Doku auf eigenem Server, öffentlich zugänglich</td></tr><tr><td>2026-02-04</td><td>CI/CD & Release</td><td>release-please, GitHub Workflows, automatisches npm publish</td></tr><tr><td>2026-02-04</td><td>Erweiterte QA</td><td>Visual Regression, A11y Tests, Bundle Size Check</td></tr><tr><td>2026-02-04</td><td>Migrations-Absicherung</td><td>Feature Parity Checklist, Deprecation Warnings</td></tr><tr><td>2026-02-04</td><td>Naming & Paket</td><td>@ocelot-social/ui, Os-Prefix</td></tr><tr><td>2026-02-04</td><td>Plattform</td><td>Modern Browsers, SSR-kompatibel, Dark Mode</td></tr><tr><td>2026-02-04</td><td>Build & Icons</td><td>Dual-Build (Tailwind + CSS), Hybrid-Architektur (~10 System-Icons)</td></tr><tr><td>2026-02-04</td><td>Organisatorisch</td><td>Apache 2.0, bleibt im Monorepo</td></tr><tr><td>2026-02-04</td><td>Konventionen</td><td>script setup, Englisch, npm, 100% Coverage, PascalCase</td></tr><tr><td>2026-02-04</td><td>Weitere</td><td>Nur Props (kein i18n), Tailwind Breakpoints, strict TS</td></tr><tr><td>2026-02-04</td><td>Ordnerstruktur</td><td>packages/ui statt styleguide-vue3</td></tr><tr><td>2026-02-04</td><td>Konfliktanalyse</td><td>Vue 2.7 Upgrade als Voraussetzung für script setup</td></tr><tr><td>2026-02-04</td><td>Webapp-Integration</td><td>Nuxt Alias für lokale Entwicklung, Git-basierter Release-Check</td></tr><tr><td>2026-02-04</td><td>Prozesse</td><td>QA-Schritt pro Komponente, Komponenten-Protokoll, KATALOG.md</td></tr><tr><td>2026-02-04</td><td>Fortschritt</td><td>Berechenbar für Gesamt und Einzelschritte</td></tr><tr><td>2026-02-04</td><td><strong>Phase 1 abgeschlossen</strong></td><td>Vue 2.7 Upgrade erfolgreich, alle Tests bestanden</td></tr><tr><td>2026-02-04</td><td><strong>Icon-Architektur</strong></td><td>Hybrid-Ansatz: ~10 System-Icons in Library, Feature-Icons in App</td></tr><tr><td>2026-02-04</td><td><strong>Dokumentationsstrategie</strong></td><td>Hybrid: Generiert (vue-component-meta) + Manuell, CI-geprüft</td></tr><tr><td>2026-02-04</td><td><strong>Abgrenzung Library/Webapp</strong></td><td>Entscheidungsbaum + Checkliste für Komponenten-Zuordnung</td></tr><tr><td>2026-02-04</td><td><strong>Externe Abhängigkeit</strong></td><td>eslint-config-it4c blockiert Linting-Setup, Workaround dokumentiert</td></tr><tr><td>2026-02-04</td><td><strong>Kompatibilitätstests</strong></td><td>4er-Matrix (Vue 2/3 × Tailwind/CSS), Example Apps, Playwright E2E</td></tr><tr><td>2026-02-07</td><td><strong>Storybook statt Histoire</strong></td><td>Histoire 1.0-beta hängt mit Tailwind v4 + Node 25 (kein Output, 100% CPU). Storybook 10 funktioniert sofort.</td></tr><tr><td>2026-02-07</td><td><strong>Storybook Greyscale-Theme</strong></td><td>Komponenten in Graustufen - verdeutlicht, dass Farben von der App kommen, nicht von der Library.</td></tr><tr><td>2026-02-07</td><td><strong>CSS Token-System</strong></td><td>requiredCssVariables mit 18 Farb-Variablen (6 Farben × 3 Werte) befüllt.</td></tr><tr><td>2026-02-07</td><td><strong>Storybook Workflow</strong></td><td>ui-storybook.yml für Build + Artifact Upload.</td></tr><tr><td>2026-02-07</td><td><strong>Docker Setup</strong></td><td>Dockerfile (dev + prod), ui-docker.yml Workflow, docker-compose Services.</td></tr><tr><td>2026-02-07</td><td><strong>Storybook Build Fix</strong></td><td>viteFinal entfernt vite-plugin-dts und build-css für Storybook-Build. Stories aus Coverage ausgeschlossen.</td></tr><tr><td>2026-02-07</td><td><strong>Docs-Check Script</strong></td><td>scripts/check-docs.ts prüft: Story-Existenz, JSDoc für Props, Varianten-Abdeckung. ui-docs.yml Workflow.</td></tr><tr><td>2026-02-04</td><td><strong>Phasen umbenannt</strong></td><td>0.5→1, 1→2, 2→3, 3→4, 4→5 (nur ganzzahlige Phasen)</td></tr><tr><td>2026-02-04</td><td><strong>Dokument-Konsolidierung</strong></td><td>§13 Zahlen korrigiert, §14 Link entfernt, §16 Reihenfolge, Terminologie vereinheitlicht</td></tr><tr><td>2026-02-04</td><td><strong>Komplexitätsanalyse</strong></td><td>§20 hinzugefügt: Risikofaktoren, Parallelisierbarkeit, Aufwandstreiber pro Komponente</td></tr><tr><td>2026-02-04</td><td><strong>Phase 2 gestartet</strong></td><td>Vite + Vue 3 Projekt initialisiert, vue-demi, Vitest, Package-Struktur</td></tr><tr><td>2026-02-04</td><td><strong>Build-System</strong></td><td>vite.config.ts mit Library-Mode, vite-plugin-dts für Types, vite-tsconfig-paths</td></tr><tr><td>2026-02-04</td><td><strong>Testing</strong></td><td>Vitest in vite.config.ts integriert, Plugin-Tests geschrieben</td></tr><tr><td>2026-02-04</td><td><strong>Dokumentation</strong></td><td>README.md mit Installation und Usage (Tree-Shaking vs Plugin)</td></tr><tr><td>2026-02-04</td><td><strong>Tailwind-Konventionen</strong></td><td>Size, Rounded, Shadow, Variant - vollständige Skalen, Dark Mode via CSS</td></tr><tr><td>2026-02-04</td><td><strong>Tailwind v4 Setup</strong></td><td>@tailwindcss/vite Plugin, Dual-Build (style.css + tailwind.preset)</td></tr><tr><td>2026-02-04</td><td><strong>Prop-Types</strong></td><td>src/types.d.ts mit Size, Rounded, Shadow, Variant</td></tr><tr><td>2026-02-04</td><td><strong>Branding-Architektur</strong></td><td>Keine Defaults in Library, Webapp definiert Branding, validateCssVariables()</td></tr><tr><td>2026-02-07</td><td><strong>ESLint Setup</strong></td><td>eslint-config-it4c v0.8.0 eingerichtet (Vue 3, Vitest, Prettier)</td></tr><tr><td>2026-02-07</td><td><strong>GitHub Workflows</strong></td><td>ui-lint.yml, ui-test.yml (100% Coverage), ui-build.yml (Build + Verify)</td></tr><tr><td>2026-02-07</td><td><strong>.tool-versions</strong></td><td>Node 25.5.0 zentral definiert, Workflows nutzen node-version-file</td></tr><tr><td>2026-02-07</td><td><strong>Vue 2/3 Matrix</strong></td><td>vue2 + @vitejs/plugin-vue2, CI Matrix-Tests, .npmrc legacy-peer-deps</td></tr><tr><td>2026-02-07</td><td><strong>Vue 2 Test-Strategie geändert</strong></td><td>Inline-Matrix entfernt (Peer-Dependency-Konflikte), Vue 2 wird via Example Apps getestet</td></tr><tr><td>2026-02-07</td><td><strong>Example Apps erstellt</strong></td><td>vue2-app und vue3-app mit Vitest, ui-compatibility.yml Workflow</td></tr><tr><td>2026-02-07</td><td><strong>ESLint Examples</strong></td><td>Eigene eslint.config.ts + prettier.config.mjs pro Example App, Lint im Compatibility-Workflow</td></tr><tr><td>2026-02-07</td><td><strong>Type Assertions</strong></td><td><code>as unknown as Plugin</code> für CI-Kompatibilität bei verlinkten Packages</td></tr><tr><td>2026-02-07</td><td><strong>Package-Validierung</strong></td><td>publint + arethetypeswrong, separate .d.cts für CJS, afterBuild Hook in vite-plugin-dts</td></tr><tr><td>2026-02-07</td><td><strong>4er Example Apps Matrix</strong></td><td>vue3-tailwind, vue3-css, vue2-tailwind, vue2-css; Workflow mit Matrix-Strategie</td></tr><tr><td>2026-02-07</td><td><strong>Release & Publish</strong></td><td>release-please Manifest-Config, ui-release.yml Workflow mit npm publish</td></tr><tr><td>2026-02-07</td><td><strong>CONTRIBUTING.md</strong></td><td>Entwickler-Leitfaden mit Setup, Komponenten-Erstellung, Code-Standards, Testing, Commits</td></tr><tr><td>2026-02-07</td><td><strong>Dependabot</strong></td><td>Konfiguration für UI-Package und alle 4 Example Apps hinzugefügt</td></tr><tr><td>2026-02-07</td><td><strong>CSS-Build</strong></td><td>Tailwind CLI im closeBundle Hook für style.css Generierung</td></tr><tr><td>2026-02-07</td><td><strong>CVA Setup</strong></td><td>class-variance-authority, clsx, tailwind-merge als Dependencies</td></tr><tr><td>2026-02-07</td><td><strong>cn() Utility</strong></td><td>Tailwind-Klassen-Merge Funktion (clsx + tailwind-merge) mit Tests</td></tr><tr><td>2026-02-07</td><td><strong>OsButton</strong></td><td>Erste Komponente mit CVA-Varianten (variant, size, fullWidth) implementiert</td></tr><tr><td>2026-02-07</td><td><strong>ESLint Fixes</strong></td><td>vue/max-attributes-per-line off (Prettier), import-x/no-relative-parent-imports off</td></tr><tr><td>2026-02-07</td><td><strong>STATUS.md Konvention</strong></td><td>Komponenten-Status als STATUS.md im Komponenten-Ordner (Colocation statt docs/)</td></tr><tr><td>2026-02-07</td><td><strong>OsButton STATUS.md</strong></td><td>Status-Datei erstellt mit Feature-Übersicht, fehlenden Features, Test-Coverage, Architektur</td></tr><tr><td>2026-02-07</td><td><strong>CVA-Dokumentation</strong></td><td>§5 erweitert: CVA + Tailwind + CSS-Variablen Architektur, Branding-Überschreibbarkeit</td></tr><tr><td>2026-02-07</td><td><strong>Accessibility Tests</strong></td><td>vitest-axe mit axe-core, Custom TypeScript-Declarations, OsButton a11y-Tests</td></tr><tr><td>2026-02-07</td><td><strong>Completeness Check</strong></td><td>check-docs.ts → check-completeness.ts, docs:check → verify, ui-docs.yml → ui-verify.yml</td></tr><tr><td>2026-02-07</td><td><strong>Visual Regression Tests</strong></td><td>Playwright mit colocated Tests (*.visual.spec.ts), ui-visual.yml Workflow</td></tr><tr><td>2026-02-07</td><td><strong>Colocated Screenshots</strong></td><td><strong>screenshots</strong>/ im Komponenten-Ordner statt separatem e2e/</td></tr><tr><td>2026-02-07</td><td><strong>Visual Test Coverage</strong></td><td>verify prüft ob alle Stories Visual Tests haben (--kebab-case URL)</td></tr><tr><td>2026-02-07</td><td><strong>Phase 2 abgeschlossen</strong></td><td>Alle 26 Aufgaben erledigt, bereit für Phase 3</td></tr><tr><td>2026-02-08</td><td><strong>Keyboard Tests</strong></td><td>describe('keyboard accessibility') mit 4 Tests, verify prüft Existenz</td></tr><tr><td>2026-02-08</td><td><strong>ESLint Plugins</strong></td><td>eslint-plugin-storybook installiert, flat/recommended Config</td></tr><tr><td>2026-02-08</td><td><strong>A11y Konsolidierung</strong></td><td>vitest-axe → @axe-core/playwright, checkA11y() in Visual Tests</td></tr><tr><td>2026-02-08</td><td><strong>Greyscale Theme WCAG</strong></td><td>Farben für 4.5:1 Kontrast angepasst (warning, info, secondary)</td></tr><tr><td>2026-02-08</td><td><strong>Test-Vereinfachung</strong></td><td>*.a11y.spec.ts entfernt, A11y via checkA11y() in Visual Tests</td></tr><tr><td>2026-02-08</td><td><strong>Completeness Update</strong></td><td>verify prüft: Story, Visual, checkA11y(), Keyboard, Varianten</td></tr><tr><td>2026-02-08</td><td><strong>Phase 2 Konsolidierung</strong></td><td>Dependencies aufgeräumt (vitest-axe, axe-core entfernt)</td></tr><tr><td>2026-02-08</td><td><strong>Projekt-Optimierung</strong></td><td>src/test/setup.ts entfernt, @storybook/vue3 entfernt, README.md fix</td></tr><tr><td>2026-02-08</td><td><strong>Package Updates</strong></td><td>size-limit 12.0.0, eslint-plugin-jsdoc 62.5.4, vite-tsconfig-paths 6.1.0</td></tr><tr><td>2026-02-08</td><td><strong>TODO: eslint-config-it4c</strong></td><td>Muss auf ESLint 10 aktualisiert werden (aktuell inkompatibel)</td></tr><tr><td>2026-02-08</td><td><strong>Phase 3: vue-demi Integration</strong></td><td>vue-demi zur Webapp hinzugefügt, Webpack-Alias für Vue 2.7 Kompatibilität</td></tr><tr><td>2026-02-08</td><td><strong>Phase 3: Webpack-Alias</strong></td><td>@ocelot-social/ui$ und style.css$ Aliase für yarn-linked Package</td></tr><tr><td>2026-02-08</td><td><strong>Phase 3: isVue2 Render</strong></td><td>OsButton mit isVue2 Check: Vue 2 attrs-Objekt, Vue 3 flat props</td></tr><tr><td>2026-02-08</td><td><strong>Phase 3: CSS-Spezifität</strong></td><td>UI-Library CSS nach Styleguide laden (styleguide.js Plugin)</td></tr><tr><td>2026-02-08</td><td><strong>Phase 3: Jest vue-demi</strong></td><td>Custom Mock (<code>__mocks__/@ocelot-social/ui.js</code>) mit Module._load Patch, defineComponent von Vue.default, vueDemiSetup.js, 979 Tests ✅</td></tr><tr><td>2026-02-08</td><td><strong>Phase 3: Button-Styles</strong></td><td>Variants angepasst: font-semibold, rounded, box-shadow, h-[37.5px]</td></tr><tr><td>2026-02-08</td><td><strong>Phase 3: Erste Integration</strong></td><td>UserTeaserPopover.vue verwendet <code><os-button></code></td></tr><tr><td>2026-02-08</td><td><strong>Phase 3: Visueller Test</strong></td><td>Manueller Vergleich OsButton vs ds-button erfolgreich ✅</td></tr><tr><td>2026-02-08</td><td><strong>Phase 3: v8 ignore</strong></td><td>Vue 2 Branch in OsButton mit <code>/* v8 ignore */</code> für 100% Coverage in Vitest</td></tr><tr><td>2026-02-08</td><td><strong>Phase 3: Docker Build</strong></td><td>ui-library Stage in Dockerfile + Dockerfile.maintenance, COPY --from=ui-library</td></tr><tr><td>2026-02-08</td><td><strong>Phase 3: CI-Fix</strong></td><td>Relativer Pfad <code>file:../packages/ui</code> statt absolut für yarn install außerhalb Docker</td></tr><tr><td>2026-02-08</td><td><strong>Phase 3: Storybook Fix</strong></td><td>TypeScript-Fehler in Stories behoben (<code>default</code> aus args entfernt)</td></tr><tr><td>2026-02-08</td><td><strong>Phase 3: attrs/listeners</strong></td><td>OsButton forwarded jetzt attrs + $listeners für Vue 2 (getCurrentInstance)</td></tr><tr><td>2026-02-08</td><td><strong>Phase 3: Jest Mock erweitert</strong></td><td>Alle Composition API Funktionen (computed, ref, watch, etc.) im Mock</td></tr><tr><td>2026-02-08</td><td><strong>Phase 3: 15 Buttons migriert</strong></td><td>GroupForm, EmbedComponent, DonationInfo, CommentCard, MapStylesButtons, GroupMember, embeds, notifications, privacy, terms-and-conditions-confirm</td></tr><tr><td>2026-02-08</td><td><strong>Phase 3: Test-Updates</strong></td><td>privacy.spec.js Selektoren, notifications Snapshot, DonationInfo.spec.js</td></tr><tr><td>2026-02-08</td><td><strong>OsButton: appearance Prop</strong></td><td>Neue <code>appearance</code> Prop: <code>filled</code> (default), <code>outline</code>, <code>ghost</code> - ermöglicht base-button Stile</td></tr><tr><td>2026-02-08</td><td><strong>OsButton: xs Size</strong></td><td>Exakte Pixel-Werte für base-button --small: h-26px, px-8px, text-12px, rounded-5px</td></tr><tr><td>2026-02-08</td><td><strong>OsButton: outline primary</strong></td><td>Grüner Rahmen + grüner Text + hellgrüner Hintergrund-Tint (rgba(25,122,49,0.18))</td></tr><tr><td>2026-02-08</td><td><strong>OsButton: ghost primary</strong></td><td>Transparenter Hintergrund, grüner Text, Hover füllt grün, Active dunkler</td></tr><tr><td>2026-02-08</td><td><strong>OsButton: Focus Style</strong></td><td><code>focus:outline-dashed focus:outline-1</code> statt ring (wie base-button)</td></tr><tr><td>2026-02-08</td><td><strong>OsButton: Active State</strong></td><td><code>active:bg-[var(--color-*-hover)]</code> für dunkleren Hintergrund beim Drücken</td></tr><tr><td>2026-02-08</td><td><strong>Visuelle Validierung</strong></td><td>Tracking-Tabelle in PROJEKT.md für manuelle Button-Vergleiche (4/16 validiert)</td></tr><tr><td>2026-02-08</td><td><strong>Storybook Grayscale Theme</strong></td><td>Vollständige CSS-Variablen: default, active-states, contrast-inverse</td></tr><tr><td>2026-02-08</td><td><strong>Tailwind Source Filter</strong></td><td><code>@import "tailwindcss" source(none)</code> - verhindert Markdown-Scanning</td></tr><tr><td>2026-02-08</td><td><strong>Button Variants Konsistenz</strong></td><td>Alle 21 compound variants mit korrekten active-states (<code>--color-*-active</code>)</td></tr><tr><td>2026-02-08</td><td><strong>CSS-Variablen erweitert</strong></td><td><code>--color-secondary/warning/success/info-active</code> in ocelot-ui-variables.scss</td></tr><tr><td>2026-02-08</td><td><strong>Story Dokumentation</strong></td><td>"Medium (37.5px)" → "Medium (36px)" korrigiert</td></tr><tr><td>2026-02-08</td><td><strong>Playwright Toleranz</strong></td><td><code>maxDiffPixelRatio: 0.03</code> für Cross-Platform Font-Rendering</td></tr><tr><td>2026-02-09</td><td><strong>Disabled-Styles korrigiert</strong></td><td>CSS-Variablen <code>--color-disabled</code>, filled: grauer Hintergrund statt opacity</td></tr><tr><td>2026-02-09</td><td><strong>terms-and-conditions-confirm</strong></td><td>Read T&C Button → <code>appearance="outline" variant="primary"</code></td></tr><tr><td>2026-02-09</td><td><strong>Visuelle Validierung</strong></td><td>10/16 Buttons validiert (terms-and-conditions-confirm.vue abgeschlossen)</td></tr><tr><td>2026-02-09</td><td><strong>Disabled:active/hover Fix</strong></td><td>CSS-Regeln in index.css mit höherer Spezifität für sofortige disabled-Darstellung</td></tr><tr><td>2026-02-09</td><td><strong>notifications.vue</strong></td><td>Check All + Uncheck All → <code>appearance="outline" variant="primary"</code></td></tr><tr><td>2026-02-09</td><td><strong>Visuelle Validierung</strong></td><td>14/16 Buttons validiert (notifications.vue abgeschlossen)</td></tr><tr><td>2026-02-09</td><td><strong>embeds.vue</strong></td><td>Allow All → <code>appearance="outline" variant="primary"</code></td></tr><tr><td>2026-02-09</td><td><strong>Disabled Border-Fix</strong></td><td>CSS-Regeln in index.css: <code>border-style: solid</code> + <code>border-width: 0.8px</code> bei :disabled</td></tr><tr><td>2026-02-09</td><td><strong>Visuelle Validierung abgeschlossen</strong></td><td>16/16 Buttons validiert (100%) ✅ Milestone 5 erfolgreich</td></tr><tr><td>2026-02-09</td><td><strong>Button-Analyse erweitert</strong></td><td>14 weitere Buttons identifiziert (ohne icon/circle/loading) → Scope: 16/35</td></tr><tr><td>2026-02-09</td><td><strong>Scope auf ~90 erweitert</strong></td><td>~60 weitere Buttons mit icon/circle/loading identifiziert</td></tr><tr><td>2026-02-09</td><td><strong>Milestone 4a: 8 Buttons</strong></td><td>DisableModal, DeleteUserModal, ReleaseModal, ContributionForm, EnterNonce, MySomethingList, ImageUploader (2x)</td></tr><tr><td>2026-02-09</td><td><strong>ImageUploader CSS-Fix</strong></td><td><code>position: absolute !important</code> für crop-confirm (überschreibt OsButton <code>relative</code>)</td></tr><tr><td>2026-02-09</td><td><strong>§16a hinzugefügt</strong></td><td>Webapp ↔ Maintenance Code-Sharing: Webapp als Source of Truth (Entscheidung #69)</td></tr><tr><td>2026-02-09</td><td><strong>§16b hinzugefügt</strong></td><td>Daten-Entkopplung: ViewModel/Mapper Pattern für API-agnostische Komponenten (Entscheidung #70)</td></tr><tr><td>2026-02-09</td><td><strong>NotificationMenu.vue</strong></td><td>2 Buttons migriert (ghost primary), padding-top Fix für vertical-align Unterschied</td></tr><tr><td>2026-02-09</td><td><strong>Milestone 4a abgeschlossen</strong></td><td>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)</td></tr><tr><td>2026-02-10</td><td><strong>Wasserfarben-Farbschema</strong></td><td>Greyscale-Theme → Aquarell-Farben (Ultramarin, Dioxazin-Violett, Alizarin, Ocker, Viridian, Cöruleum), WCAG AA konform</td></tr><tr><td>2026-02-10</td><td><strong>Stories konsolidiert</strong></td><td>Primary/Secondary/Danger/Default entfernt → AllVariants; AllSizes/AllAppearances/Disabled/FullWidth zeigen alle 7 Varianten</td></tr><tr><td>2026-02-10</td><td><strong>Appearance: Filled/Outline/Ghost</strong></td><td>Einzelne Stories umbenannt und mit allen 7 Varianten erweitert</td></tr><tr><td>2026-02-10</td><td><strong>Playground-Story</strong></td><td>Interaktive Controls (argTypes nur in Playground, nicht global)</td></tr><tr><td>2026-02-10</td><td><strong>Einheitlicher Border</strong></td><td><code>border-[0.8px] border-solid border-transparent</code> als Base-Klasse für alle Appearances</td></tr><tr><td>2026-02-10</td><td><strong>WCAG 2.4.7 Fix</strong></td><td>Default-Variante: <code>focus:outline-none</code> → <code>focus:outline-dashed focus:outline-current</code></td></tr><tr><td>2026-02-10</td><td><strong>Keyboard A11y Test</strong></td><td>Playwright-Test fokussiert alle Buttons und prüft <code>outlineStyle !== 'none'</code></td></tr><tr><td>2026-02-10</td><td><strong>data-appearance Attribut</strong></td><td>OsButton rendert <code>data-appearance</code> auf <code><button></code>; CSS-Selektoren nutzen <code>[data-appearance="filled"]</code> statt escaped Tailwind-Klassen</td></tr><tr><td>2026-02-10</td><td><strong>Code-Review Fixes</strong></td><td>Unit-Tests: spezifischere Assertions (Compound-Variant-Logik), Trailing Spaces in Testnamen, ESLint restrict-template-expressions Fix</td></tr><tr><td>2026-02-10</td><td><strong>CSS-Linting</strong></td><td><code>@eslint/css</code> + <code>tailwind-csstree</code> für Tailwind v4 Custom Syntax; <code>excludeCSS()</code> Helper verhindert JS-Regel-Konflikte; Regeln: no-empty-blocks, no-duplicate-imports, no-invalid-at-rules</td></tr><tr><td>2026-02-10</td><td><strong>CI-Workflow-Trigger</strong></td><td>9 UI-Workflows von <code>on: push</code> auf <code>push</code>+<code>pull_request</code> mit Branch-Filter (<code>master</code>) und Path-Filter (<code>packages/ui/**</code> + Workflow-Datei) umgestellt</td></tr><tr><td>2026-02-10</td><td><strong>custom-class entfernt</strong></td><td><code>custom-class</code> Prop (entfernt aus OsButton) → <code>class</code> Attribut in notifications.vue, MapStylesButtons.vue, EmbedComponent.vue (4 Stellen); Snapshot aktualisiert</td></tr><tr><td>2026-02-10</td><td><strong>Vue 3 Template-Fix</strong></td><td><code>this.$t()</code> → <code>$t()</code> in CommentCard.vue (this im Template in Vue 3 nicht verfügbar)</td></tr><tr><td>2026-02-11</td><td><strong>Icon-Slot implementiert</strong></td><td>Benannter <code>#icon</code> Slot für OsButton, slot-basiert statt Icon-Prop (icon-system-agnostisch)</td></tr><tr><td>2026-02-11</td><td><strong>Icon-Wrapper Klassen</strong></td><td>Tailwind-Utility-Klassen direkt auf <code><span></code>: <code>inline-flex items-center shrink-0 h-[1.2em] [&>svg]:h-full [&>svg]:w-auto [&>svg]:fill-current</code></td></tr><tr><td>2026-02-11</td><td><strong>VNode Text-Erkennung</strong></td><td><code>hasText</code> prüft VNode-Children auf sichtbaren Inhalt; whitespace-only → icon-only Verhalten</td></tr><tr><td>2026-02-11</td><td><strong>Gap & Margin Logik</strong></td><td><code>gap-2</code> bei Icon+Text, <code>-ml-1</code> bei Icon, <code>-ml-1 -mr-1</code> bei Icon-Only (optischer Ausgleich)</td></tr><tr><td>2026-02-11</td><td><strong>4 neue Stories</strong></td><td>Icon, IconOnly, IconSizes, IconAppearances mit Inline-SVG Komponenten (CheckIcon, CloseIcon, PlusIcon)</td></tr><tr><td>2026-02-11</td><td><strong>Playground erweitert</strong></td><td>Reaktiver Icon-Selektor (none/check/close/plus) + Label-Text-Control via <code>computed()</code></td></tr><tr><td>2026-02-11</td><td><strong>Storybook: components Option</strong></td><td>Funktionale Komponenten müssen in <code>components</code> registriert werden, nicht in <code>setup()</code> return</td></tr><tr><td>2026-02-11</td><td><strong>Storybook: CSS nicht in index.css</strong></td><td>Storybook lädt eigene <code>storybook.css</code>, nicht <code>src/styles/index.css</code> → Utility-Klassen direkt verwenden</td></tr><tr><td>2026-02-11</td><td><strong>SVG-Targeting</strong></td><td><code>[&>svg]</code> statt <code>[&>*]</code> für Icon-Sizing (BaseIcon rendert <code><span><svg></code>, Wrapper-Span darf nicht beeinflusst werden)</td></tr><tr><td>2026-02-11</td><td><strong>my-email-address migriert</strong></td><td>Save-Button: <code><os-button variant="primary"></code> mit <code><template #icon><base-icon name="check" /></template></code></td></tr><tr><td>2026-02-11</td><td><strong>Code-Optimierung</strong></td><td><code>ICON_CLASS</code> Konstante extrahiert, <code>iconMargin</code> Variable, vereinfachte <code>hasText</code>-Logik (kein Symbol.for)</td></tr><tr><td>2026-02-11</td><td><strong>Größenabhängiger Gap</strong></td><td><code>gap-1</code> (4px) für xs/sm, <code>gap-2</code> (8px) für md/lg/xl bei Icon+Text</td></tr><tr><td>2026-02-11</td><td><strong>Größenabhängiger Margin</strong></td><td>Kein negativer Icon-Margin bei xs/sm (voller Padding-Abstand zur Button-Grenze)</td></tr><tr><td>2026-02-11</td><td><strong>DisableModal.vue</strong></td><td>Confirm-Button migriert: <code>danger filled icon="exclamation-circle"</code> → <code>variant="danger"</code> + <code>#icon</code> Slot</td></tr><tr><td>2026-02-11</td><td><strong>DeleteUserModal.vue</strong></td><td>Confirm-Button migriert: identisches Pattern wie DisableModal</td></tr><tr><td>2026-02-11</td><td><strong>CtaUnblockAuthor.vue</strong></td><td>Button migriert: <code>filled icon="arrow-right"</code> → <code>variant="primary"</code> + <code>#icon</code> Slot, OsButton importiert</td></tr><tr><td>2026-02-11</td><td><strong>LocationSelect.vue</strong></td><td>Icon-only Close-Button migriert: <code>ghost size="small" icon="close"</code> → <code>variant="primary" appearance="ghost" size="sm"</code> + aria-label</td></tr><tr><td>2026-02-11</td><td><strong>CategoriesSelect.vue</strong></td><td>v-for Buttons migriert: dynamisches <code>:icon</code> → <code>#icon</code> Slot, <code>:filled</code> → <code>:appearance</code>, CSS <code>.base-button</code> → <code>button</code></td></tr><tr><td>2026-02-11</td><td><strong>profile/_id/_slug.vue</strong></td><td>Chat-Button migriert: <code>icon="chat-bubble"</code> → <code>variant="primary" appearance="outline" full-width</code> + <code>#icon</code> Slot</td></tr><tr><td>2026-02-11</td><td><strong>verify.vue korrigiert</strong></td><td>Kein Button vorhanden (Eintrag aus Milestone-Liste entfernt)</td></tr><tr><td>2026-02-11</td><td><strong>PaginationButtons.vue</strong></td><td>2 circle icon-only Buttons migriert: <code>outline primary circle</code> + <code>#icon</code> Slot + aria-label</td></tr><tr><td>2026-02-11</td><td><strong>OsButton: circle Prop</strong></td><td><code>circle</code> Prop: <code>rounded-full p-0</code> + größenabhängige Breiten (CIRCLE_WIDTHS Map)</td></tr><tr><td>2026-02-11</td><td><strong>OsButton: loading Prop</strong></td><td>Animierter SVG-Spinner mit <code>aria-busy="true"</code>, Button auto-disabled bei loading</td></tr><tr><td>2026-02-11</td><td><strong>Spinner-Architektur</strong></td><td>Beide Animationen (rotate + dash) auf <code><circle></code> Element; SVG ist statischer Container; Chrome-Compositing-Bug-Workaround</td></tr><tr><td>2026-02-11</td><td><strong>Spinner-Zentrierung</strong></td><td>Icon-Buttons: Spinner über Icon (translate-basiert, overflow:visible); Text-Buttons: Spinner im Button-Container (inset-0 m-auto)</td></tr><tr><td>2026-02-11</td><td><strong>animations.css</strong></td><td>Keyframes <code>os-spinner-dash</code> + <code>os-spinner-rotate</code> in separate CSS-Datei ausgelagert</td></tr><tr><td>2026-02-11</td><td><strong>min-width pro Größe</strong></td><td><code>min-w-[26px]</code>/<code>min-w-[36px]</code>/<code>min-w-12</code>/<code>min-w-14</code> in button.variants.ts (verhindert zu kleine leere Buttons)</td></tr><tr><td>2026-02-11</td><td><strong>Code-Optimierung</strong></td><td>OsButton ~250→207 Zeilen: buttonData geteilt, SPINNER_PX vereinfacht, redundante cn() entfernt, getCurrentInstance nur Vue 2</td></tr><tr><td>2026-02-11</td><td><strong>5 neue Unit-Tests</strong></td><td>default type, data-appearance, min-w, icon-only loading, circle gap-1; gesamt: 76 Tests</td></tr><tr><td>2026-02-11</td><td><strong>Milestone 4b abgeschlossen</strong></td><td>icon ✅, circle ✅, loading ✅ — alle OsButton-Props implementiert</td></tr><tr><td>2026-02-11</td><td><strong>Milestone 4c: 59 Buttons</strong></td><td>Chat (2), AddChatRoomByUserSearch (1), CommentCard (1), CommentForm (2), ComponentSlider (2), ContributionForm (1), DeleteData (1), EmbedComponent (1), FilterMenu (1), HeaderButton (2), CategoriesFilter (2), OrderByFilter (2), EventsByFilter (2), FollowingFilter (3), GroupButton (1), ConfirmModal (2), ReportModal (2), Password/Change (1), PasswordReset/Request (1), PasswordReset/ChangePassword (1), Registration/Signup (1), ReleaseModal (1), ImageUploader (2), CreateInvitation (1), Invitation (2), ProfileList (1), ReportRow (1), MySomethingList (3), ActionButton (1), pages/index (2), profile/add-post (1), post/blur-toggle (1), groups/slug (3), settings/index (1), admin/users (2), blocked-users (1), data-download (1), muted-users (1), groups/index (1), enter-nonce (1)</td></tr><tr><td>2026-02-11</td><td><strong>type="submit" Pattern</strong></td><td>OsButton hat <code>type="button"</code> als Default; alle Form-Submit-Buttons brauchen explizit <code>type="submit"</code></td></tr><tr><td>2026-02-11</td><td><strong>!!errors Pattern</strong></td><td>DsForm <code>errors</code> ist ein Objekt, nicht Boolean; OsButton <code>disabled</code> Prop erwartet Boolean → <code>!!errors</code> nötig</td></tr><tr><td>2026-02-11</td><td><strong>CSS-Selector Pattern</strong></td><td><code>.base-button</code> → <code>> button</code> oder <code>button</code>; Position/Dimensions brauchen <code>!important</code> für Tailwind-Override</td></tr><tr><td>2026-02-11</td><td><strong>Disabled border-color</strong></td><td>Outline disabled border von <code>var(--color-disabled)</code> auf <code>var(--color-disabled-border,#e5e3e8)</code> mit Fallback</td></tr><tr><td>2026-02-11</td><td><strong>Phase 3 abgeschlossen</strong></td><td>132 <code><os-button></code> Tags in 78 Dateien, 0 <code><base-button></code> in Templates verbleibend</td></tr><tr><td>2026-02-11</td><td><strong>Password/Change.vue Fix</strong></td><td><code>!!errors</code> für disabled-Prop (DsForm errors ist Objekt)</td></tr><tr><td>2026-02-11</td><td><strong>CommentForm.vue Fix</strong></td><td><code>type="submit"</code> fehlte + <code>!!errors</code> für disabled-Prop</td></tr><tr><td>2026-02-11</td><td><strong>GroupForm.vue ds-button</strong></td><td>Letzter <code><ds-button></code> in Webapp → <code><os-button></code> mit <code>#icon</code> Slot migriert</td></tr><tr><td>2026-02-11</td><td><strong>OsButton.spec.ts TS-Fix</strong></td><td><code>size</code> aus <code>Object.entries</code> als Union Type gecastet (`as 'sm'</td></tr><tr><td>2026-02-11</td><td><strong>Coverage 100%</strong></td><td><code>v8 ignore start/stop</code> für Vue 2 Branch, <code>v8 ignore next</code> für defensive `</td></tr><tr><td>2026-02-11</td><td><strong>Scope: 133 Buttons</strong></td><td>133 <code><os-button></code> Tags in 79 Dateien, 0 <code><base-button></code> + 0 <code><ds-button></code> verbleibend</td></tr><tr><td>2026-02-12</td><td><strong>data-variant Attribut</strong></td><td>OsButton rendert <code>data-variant</code> auf <code><button></code> (konsistent mit <code>data-appearance</code>), ermöglicht CSS-Selektoren wie <code>button[data-variant="danger"]</code></td></tr><tr><td>2026-02-12</td><td><strong>notifications.spec.js</strong></td><td>Test-API korrigiert: <code>wrapper.find()</code> (Vue Test Utils) → <code>screen.getByText()</code> (Testing Library), <code>button.disabled</code> statt <code>button.attributes('disabled')</code></td></tr><tr><td>2026-02-12</td><td><strong>FilterMenu Regressionsbug</strong></td><td><code>appearance="ghost"</code> war hardcoded statt dynamisch; <code>filterActive</code> Computed Property existierte aber war nicht genutzt → <code>:appearance="filterActive ? 'filled' : 'ghost'"</code></td></tr><tr><td>2026-02-12</td><td><strong>FilterMenu.spec.js</strong></td><td>Test von CSS-Klasse <code>--filled</code> auf <code>data-appearance="filled"</code> Attribut-Selektor umgestellt</td></tr><tr><td>2026-02-12</td><td><strong>CtaUnblockAuthor.vue</strong></td><td>Typo <code>require: true</code> → <code>required: true</code> (Vue ignorierte die Prop-Validierung)</td></tr><tr><td>2026-02-12</td><td><strong>LocationSelect.vue Fixes</strong></td><td><code>event.target.value</code> → <code>this.currentValue</code> (Button hat kein value), <code>@click.native</code> → <code>@click</code> (Vue 3), <code>aria-label</code> via i18n</td></tr><tr><td>2026-02-12</td><td><strong>i18n: actions.clear</strong></td><td>Neuer Key in allen 9 Sprachdateien: en=Clear, de=Zurücksetzen, fr=Effacer, es=Borrar, it=Cancella, nl=Wissen, pl=Wyczyść, pt=Limpar, ru=Очистить</td></tr><tr><td>2026-02-12</td><td><strong>OsButton JSDoc</strong></td><td>Slot-Dokumentation (<code>@slot default</code>, <code>@slot icon</code>) für vue-component-meta/Storybook autodocs</td></tr><tr><td>2026-02-12</td><td><strong>OsButton xs entfernt</strong></td><td><code>isSmall</code> von <code>['xs', 'sm'].includes(size)</code> auf <code>size === 'sm'</code> vereinfacht (xs ist kein gültiger Size-Wert)</td></tr><tr><td>2026-02-12</td><td><strong>Strikte Typisierung</strong></td><td><code>type Size = NonNullable<ButtonVariants['size']></code>, <code>Record<Size, ...></code> für CIRCLE_WIDTHS + SPINNER_PX; <code>props.size!</code> → <code>(props.size ?? 'md') as Size</code></td></tr><tr><td>2026-02-12</td><td><strong>animations.css</strong></td><td>Stylelint-konforme Formatierung: eine Deklaration pro Zeile, Leerzeilen zwischen Keyframe-Stufen</td></tr><tr><td>2026-02-12</td><td><strong>OsButton Refactoring</strong></td><td><code>vueAttrs()</code> Helper für Vue 2/3 Attribut-Handling, Einmal-Variablen durch <code>cn()</code> ersetzt, <code>children</code> inline; 77 Tests, 100% Coverage</td></tr><tr><td>2026-02-12</td><td><strong>CSS @import Reihenfolge</strong></td><td><code>@import "./animations.css"</code> vor <code>@source</code>-Direktiven verschoben (CSS-Spec: @import vor anderen At-Rules)</td></tr><tr><td>2026-02-12</td><td><strong>CustomButton Cleanup</strong></td><td><code>isEmpty</code> aus <code>data()</code> entfernt — reine Utility-Funktion braucht keine Vue-Reaktivität</td></tr><tr><td>2026-02-12</td><td><strong>notifications.spec.js</strong></td><td>Doppelten <code>beforeEach</code> konsolidiert; <code>wrapper</code> von Modulebene in <code>describe</code>-Block verschoben</td></tr><tr><td>2026-02-12</td><td><strong>Style-Scoping</strong></td><td>MenuLegend.vue: <code><style scoped></code> hinzugefügt; ReportModal + DeleteUserModal: CSS-Selektoren mit Komponenten-Prefix</td></tr><tr><td>2026-02-12</td><td><strong>data-test Selektoren</strong></td><td>LocationSelect (<code>clear-location-button</code>) + HashtagsFilter (<code>clear-search-button</code>): spezifischere Test-Selektoren</td></tr><tr><td>2026-02-12</td><td><strong>Vue 3 Compat Fixes</strong></td><td>FollowButton: <code>.native</code> entfernt; FilterMenu: <code>slot</code>/<code>slot-scope</code> → <code><template #default></code>; HashtagsFilter: <code>this.$t()</code> → <code>$t()</code></td></tr><tr><td>2026-02-12</td><td><strong>A11y: aria-label</strong></td><td>GroupContentMenu icon-only Button: <code>$t('group.contentMenu.menuButton')</code>; PaginationButtons: <code>$t('pagination.previous/next')</code></td></tr><tr><td>2026-02-12</td><td><strong>i18n Keys</strong></td><td><code>pagination.previous/next</code> + <code>group.contentMenu.menuButton</code> in allen 9 Sprachdateien angelegt</td></tr><tr><td>2026-02-12</td><td><strong>Modal Konsistenz</strong></td><td>DisableModal + DeleteUserModal: <code>appearance="filled"</code> + <code>:loading="loading"</code> auf Danger-Buttons</td></tr><tr><td>2026-02-12</td><td><strong>Loading State</strong></td><td>my-email-address/index.vue: <code>loadingData</code> hinzugefügt + <code>finally</code> Block für Reset</td></tr><tr><td>2026-02-12</td><td><strong>MapButton #icon Slot</strong></td><td>Icon von Default-Slot in <code><template #icon></code> verschoben (konsistent mit allen anderen Buttons)</td></tr><tr><td>2026-02-12</td><td><strong>Dead Code entfernt</strong></td><td>MySomethingList.vue: <code>.icon-button</code> CSS-Klasse (nach Migration nicht mehr verwendet)</td></tr><tr><td>2026-02-12</td><td><strong>Button-Wrapper-Analyse</strong></td><td>15 OsButton-Wrapper klassifiziert: 4 Smart (Apollo/Vuex), 4 Presentational, 7 Borderline; GroupButton + MapButton als Inline-Kandidaten identifiziert</td></tr><tr><td>2026-02-12</td><td><strong>compat/ Konzept</strong></td><td>Separates Verzeichnis für temporäre Migrations-Wrapper (nicht von check-completeness.ts erfasst); BaseIcon als erster Kandidat (131 Nutzungen)</td></tr><tr><td>2026-02-13</td><td><strong>data-test Selektoren</strong></td><td>~10 Komponenten: <code>data-test</code> Attribute für robuste Test-Selektoren (unmute-btn, unblock-btn, follow-btn, join-leave-btn, login-btn, load-all-connections-btn, content-menu-button)</td></tr><tr><td>2026-02-13</td><td><strong>Cypress Selektoren</strong></td><td>4 Step-Definitions: <code>.user-content-menu button</code> / <code>.content-menu button</code> → <code>[data-test="content-menu-button"]</code></td></tr><tr><td>2026-02-13</td><td><strong>Spec-Selektoren</strong></td><td>FollowList, FollowButton, LoginButton, CtaJoinLeaveGroup, ReportRow, muted-users, blocked-users: generische <code>button</code> → <code>[data-test="..."]</code></td></tr><tr><td>2026-02-13</td><td><strong>A11y: aria-label</strong></td><td>~15 icon-only Buttons: aria-label hinzugefügt (admin/users, AddChatRoom, EmbedComponent, groups, profile, CustomButton, HeaderMenu, ImageUploader, ContentMenu, HeaderButton, InviteButton, LoginButton, blocked/muted-users)</td></tr><tr><td>2026-02-13</td><td><strong>Zustandsabhängiges aria-label</strong></td><td>post/_id/_slug: <code>$t(blurred ? 'post.sensitiveContent.show' : 'post.sensitiveContent.hide')</code></td></tr><tr><td>2026-02-13</td><td><strong>ComponentSlider aria-label</strong></td><td>Interpoliertes Label: <code>$t('component-slider.step', { current: index + 1, total: ... })</code></td></tr><tr><td>2026-02-13</td><td><strong>i18n Keys (6 neue)</strong></td><td><code>actions.search</code>, <code>actions.close</code>, <code>actions.menu</code>, <code>site.navigation</code>, <code>post.sensitiveContent.show/hide</code>, <code>component-slider.step</code> in allen 9 Sprachdateien</td></tr><tr><td>2026-02-13</td><td><strong>Loading-State Fixes</strong></td><td>DisableModal + ReleaseModal: <code>finally { this.loading = false }</code> für Reset</td></tr><tr><td>2026-02-13</td><td><strong>Bugfixes</strong></td><td>ChangePassword: <code>!!errors</code>; Password/Change: <code>disabled</code> aus data() entfernt + 2 tote Tests; MenuBar: unbenutztes <code>ref</code> entfernt</td></tr><tr><td>2026-02-13</td><td><strong>Button-Props</strong></td><td>GroupForm cancel: <code>variant="default" appearance="filled"</code>; donations/LoginForm/EnterNonce: <code>appearance="filled"</code> ergänzt</td></tr><tr><td>2026-02-13</td><td><strong>CSS-Selektoren</strong></td><td>LoginForm: <code>.login-form button</code> → <code>.login-form button[type='submit']</code>; pages/index: redundante Klasse auf BaseIcon entfernt</td></tr><tr><td>2026-02-13</td><td><strong>JoinLeaveButton</strong></td><td><code>.native</code> von <code>@mouseenter</code>/<code>@mouseleave</code> entfernt (Vue 3 Kompatibilität)</td></tr><tr><td>2026-02-13</td><td><strong>MySomethingList</strong></td><td><code>:title</code> + <code>:aria-label</code> auf Edit/Delete-Buttons (Tooltip beibehalten neben Accessibility)</td></tr><tr><td>2026-02-13</td><td><strong>OsButton Klasse</strong></td><td><code>os-button</code> CSS-Klasse auf Button-Element für Branding-Kompatibilität (#9211)</td></tr><tr><td>2026-02-14</td><td><strong>ESLint Config Update</strong></td><td>eslint-config-it4c v0.11.2: Flat Config, path alias <code>#src</code>, CSS-Linting, security/detect-non-literal-fs-filename, n/no-sync, n/shebang (#9233)</td></tr><tr><td>2026-02-14</td><td><strong>check-completeness</strong></td><td>Parallelisiertes File-Reading, breitere Regex für Keyboard-Tests, Playground-Tests ignoriert</td></tr><tr><td>2026-02-14</td><td><strong>Release v0.0.2</strong></td><td>@ocelot-social/ui v0.0.2 veröffentlicht</td></tr><tr><td>2026-02-14</td><td><strong><code>as</code> Prop</strong></td><td>Polymorphe OsButton-Komponente: <code>as</code> Prop für dynamischen Tag/Komponente (<code>button</code>, <code>a</code>, <code>nuxt-link</code>, <code>router-link</code>); moderner Standard (Headless UI, Radix Vue)</td></tr><tr><td>2026-02-14</td><td><strong>Naming: tag → as</strong></td><td><code>tag</code> → <code>as</code> umbenannt nach Recherche moderner UI-Libraries (Headless UI, Radix Vue, Chakra UI, PrimeVue nutzen <code>as</code>)</td></tr><tr><td>2026-02-14</td><td><strong>Disabled nur für button</strong></td><td><code>disabled</code>/<code>type</code>/<code>loading</code> nur bei <code>as="button"</code> (Links haben kein natives <code>disabled</code>); <code>aria-disabled</code>/<code>tabindex</code> Logik entfernt</td></tr><tr><td>2026-02-14</td><td><strong>Polymorphic Story</strong></td><td>Neue Story <code>Polymorphic</code> mit Varianten, Icons, disabled-Vergleich; Playground mit <code>as</code>-Selektor (button/a)</td></tr><tr><td>2026-02-14</td><td><strong>nuxt-link Migration</strong></td><td>15 <code><nuxt-link></code>/<code><a></code>-Wrapper → <code>as="nuxt-link"</code>/<code>as="a"</code> in 15 Webapp-Dateien; invalides HTML (<code><button></code> in <code><a></code>) eliminiert</td></tr><tr><td>2026-02-14</td><td><strong>CustomButton konsolidiert</strong></td><td>v-if/v-else für <code><a></code>/<code><nuxt-link></code> Wrapper → einzelner <code><os-button :as="linkTag" v-bind="linkProps"></code> mit Computed Properties</td></tr><tr><td>2026-02-14</td><td><strong>NotificationMenu konsolidiert</strong></td><td>2 separate Buttons (kein Badge / mit Badge) zu einem zusammengeführt — counter-icon zeigt bei <code>count=0</code> kein Badge</td></tr><tr><td>2026-02-14</td><td><strong>CSS-Selektor Fix</strong></td><td>pages/index.vue: <code>button.post-add-button-top/bottom</code> → <code>.post-add-button-top/bottom</code> (nuxt-link rendert <code><a></code>, nicht <code><button></code>)</td></tr><tr><td>2026-02-14</td><td><strong>Profil-Spacing</strong></td><td>profile/_id/_slug.vue: <code>v-if</code> auf ds-grid-item (kein leerer Abstand), symmetrisches Padding <code>$space-x-small</code></td></tr><tr><td>2026-02-15</td><td><strong>OsIcon Komponente</strong></td><td>Neue Komponente: <code>name</code> (System-Icon), <code>icon</code> (Custom Component), <code>size</code> (xs-2xl); Vue 2/3 via vue-demi h()</td></tr><tr><td>2026-02-15</td><td><strong>vite-svg-icon Plugin</strong></td><td>Custom Vite Plugin: extrahiert viewBox + <code><path></code> aus SVG, transformiert zu Vue h()-Aufruf via <code>?icon</code> Query</td></tr><tr><td>2026-02-15</td><td><strong>System-Icons</strong></td><td>3 SVG-Icons in Library: check, close, plus (viewBox 0 0 32 32, stroke-basiert); SYSTEM_ICONS Registry + SystemIconName Type</td></tr><tr><td>2026-02-15</td><td><strong>Icon-Größen</strong></td><td>CVA-Varianten: xs(0.75em), sm(0.875em), md(1.2em default), lg(1.5em), xl(2em), 2xl(2.5em) — em-basiert für Kontext-Skalierung</td></tr><tr><td>2026-02-15</td><td><strong>Icon A11y</strong></td><td>Decorative: <code>aria-hidden="true"</code> (default); Semantic: <code>role="img"</code> + <code>aria-label</code> wenn Label-Prop vorhanden</td></tr><tr><td>2026-02-15</td><td><strong>OsIcon in OsButton</strong></td><td>OsButton nutzt OsIcon statt inline SVG-Elemente für Icon-Slot-Rendering</td></tr><tr><td>2026-02-15</td><td><strong>Ocelot-Icons</strong></td><td>Separates Entry-Point (<code>ocelot.mjs</code>): dynamisches Icon-Loading via <code>import.meta.glob('**/*.svg', { query: '?icon' })</code></td></tr><tr><td>2026-02-15</td><td><strong>webapp → ocelot</strong></td><td><code>src/webapp/</code> → <code>src/ocelot/</code> umbenannt; Stories, Tests, Exports angepasst; konsistentes Naming</td></tr><tr><td>2026-02-15</td><td><strong>OsIcon Tests</strong></td><td>211 Zeilen Unit-Tests (Rendering, Sizes, A11y, CSS, System-Icons); Visual Tests mit checkA11y(); Keyboard A11y; 100% Coverage</td></tr><tr><td>2026-02-15</td><td><strong>OsButton Stories</strong></td><td>Bereinigt: Inline-SVG-Komponenten durch OsIcon ersetzt; WithAriaLabel-Story entfernt; InheritColor-Story vereinfacht</td></tr><tr><td>2026-02-15</td><td><strong>check-completeness</strong></td><td>Erweitert für ocelot/ Verzeichnis; unterstützt OsIcon-Patterns</td></tr><tr><td>2026-02-15</td><td><strong>svg-icon.d.ts</strong></td><td>TypeScript-Deklaration für <code>?icon</code> Import-Query (Component-Typ)</td></tr><tr><td>2026-02-15</td><td><strong>BaseIcon → OsIcon Migration</strong></td><td>131 <code><base-icon></code> in 70+ Dateien → <code><os-icon :icon="icons.xxx"></code>, 0 BaseIcon verbleibend</td></tr><tr><td>2026-02-15</td><td><strong>82 Ocelot-Icons</strong></td><td>Von 1 auf 82 Icons: Feature-Icons + 17 Kategorie-Icons aus Webapp kopiert</td></tr><tr><td>2026-02-15</td><td><strong>vite-svg-icon erweitert</strong></td><td>Unterstützt jetzt <code><rect></code>, <code><circle></code>, <code><polygon></code>, <code><polyline></code>, <code><ellipse></code>, <code><line></code> (war path-only)</td></tr><tr><td>2026-02-15</td><td><strong>SVG-Minifizierung</strong></td><td>Alle 21 neuen SVGs auf Single-Line minifiziert (Multiline brach JS-String-Literale im Plugin)</td></tr><tr><td>2026-02-15</td><td><strong>Kategorie-Icons</strong></td><td>DB-String → <code>toCamelCase()</code> → <code>ocelotIcons[key]</code> Lookup in Category, CategoriesFilter, CategoriesSelect, admin/categories</td></tr><tr><td>2026-02-15</td><td><strong>MenuLegend.vue Fix</strong></td><td><code>legendItems</code> von <code>data()</code> → <code>computed</code> (<code>data()</code> läuft vor <code>created()</code>, <code>this.icons</code> undefined)</td></tr><tr><td>2026-02-15</td><td><strong>HeaderMenu.vue Fix</strong></td><td>Map-Button Icon-Größe: <code>size="xl"</code> + negative Margin (OsIcon md=1.2em vs BaseIcon --large=2.2em)</td></tr><tr><td>2026-02-15</td><td><strong>ShowPassword.vue Fix</strong></td><td><code>:data-test="iconName"</code> entfernt (Icon ist jetzt Render-Function statt String)</td></tr><tr><td>2026-02-15</td><td><strong>Test-Updates (8 Specs)</strong></td><td>Category, ProfileAvatar, CounterIcon, ReportRow, ActionButton, ComponentSlider, ShowPassword, LoginForm: BaseIcon → OsIcon + ocelotIcons</td></tr><tr><td>2026-02-15</td><td><strong>8 Snapshots gelöscht</strong></td><td>Stale Snapshot-Dateien entfernt nach BaseIcon → OsIcon Migration</td></tr><tr><td>2026-02-15</td><td><strong>CSS Migration</strong></td><td><code>.base-icon</code> → <code>.os-icon</code> in main.scss und Category/index.vue</td></tr><tr><td>2026-02-15</td><td><strong>Jest Mock ocelot</strong></td><td><code>test/__mocks__/@ocelot-social/ui/ocelot.js</code> für ocelotIcons in Jest-Umgebung</td></tr><tr><td>2026-02-18</td><td><strong>OsSpinner Komponente</strong></td><td>Neue Komponente: size (xs-2xl, em-basiert), currentColor, role="status", aria-label; Vue 2/3 via h() + isVue2</td></tr><tr><td>2026-02-18</td><td><strong>OsSpinner Decorative</strong></td><td><code>aria-hidden="true"</code> unterdrückt role/aria-label; OsButton nutzt OsSpinner als Komponente (decorative)</td></tr><tr><td>2026-02-18</td><td><strong>ButtonSize Type</strong></td><td><code>ButtonSize</code> (sm/md/lg/xl) exportiert aus button.variants.ts; types.d.ts Kommentar: Size ist Vokabular, nicht Pflicht</td></tr><tr><td>2026-02-18</td><td><strong>OsSpinner Webapp-Migration</strong></td><td>4 Stellen migriert: ImageUploader, profile, groups, admin; LoadingSpinner gelöscht</td></tr><tr><td>2026-02-18</td><td><strong>ds-space centered Bugfix</strong></td><td><code><ds-space centered></code> → <code><div style="text-align:center;padding:48px 0"></code> in 3 Seiten (Styleguide-Bug)</td></tr><tr><td>2026-02-18</td><td><strong>Admin Spinner Fix</strong></td><td><code><ApolloQuery></code> → <code>apollo</code>-Option + <code>$apollo.loading</code>; SSR-Prefetch verhinderte Loading-State im Client</td></tr><tr><td>2026-02-18</td><td><strong>filterStatistics Fix</strong></td><td><code>delete data.__typename</code> → Destructuring <code>{ __typename, ...rest }</code> (keine Mutation des Originalobjekts)</td></tr><tr><td>2026-02-18</td><td><strong>infinite-loading Spinner-Slot</strong></td><td>OsSpinner im <code>spinner</code>-Slot von vue-infinite-loading in 3 Seiten (index, profile, groups); einheitliches Spinner-Design</td></tr><tr><td>2026-02-19</td><td><strong>BaseCard → OsCard Migration</strong></td><td>~30 Webapp-Dateien: <code><base-card></code> → <code><os-card></code> mit lokalen Imports; CSS-Fixes für Tailwind p-6 Override, outline highlight, child→descendant selectors</td></tr><tr><td>2026-02-19</td><td><strong>#imageColumn/#topMenu inline</strong></td><td>LoginForm, RegistrationSlider, password-reset: BaseCard-Slots → inline Layout mit <code>.os-card.--columns</code> CSS in main.scss</td></tr><tr><td>2026-02-19</td><td><strong>Tests & Stories migriert</strong></td><td>16 Spec-Dateien, 4 Story-Dateien, 12+2 Cypress E2E Step-Definitions: base-card → os-card Selektoren</td></tr><tr><td>2026-02-19</td><td><strong>BaseCard gelöscht</strong></td><td>BaseCard.vue Komponente + base-components.js Plugin entfernt; nuxt.config, maintenance config, testSetup bereinigt</td></tr><tr><td>2026-02-19</td><td><strong>CSS-Fixes</strong></td><td>ContributionForm Media-Query Selektoren, ProfileList Spezifität, InternalPage $space-small, OsCard highlight outline-1 Tests</td></tr><tr><td>2026-02-19</td><td><strong>Code-Quality</strong></td><td>SocialMedia Props typisiert, LoginForm querySelector, redundante client-only entfernt, NotificationsTable optional chaining, HashtagsFilter doppeltes Mounting</td></tr><tr><td>2026-02-19</td><td><strong>Review Fixes (Session 26)</strong></td><td>Cypress Kind-Kombinator <code>.os-card > .title</code>, OsCard.spec.ts Bitmask-Assertion fix, Maintenance-App Abhängigkeiten als pre-existing bewertet</td></tr><tr><td>2026-02-19</td><td><strong>Tier A Migration (Session 27)</strong></td><td>10 triviale ds-* Vue-Wrapper → Plain HTML + CSS-Klassen; <code>_ds-compat.scss</code> Utility-Klassen; ~90 Dateien geändert</td></tr><tr><td>2026-02-19</td><td><strong>ds-section/placeholder/tag/list</strong></td><td>19 Nutzungen in 13 Dateien → HTML-Elemente mit bestehenden CSS-Klassen aus system.css</td></tr><tr><td>2026-02-19</td><td><strong>ds-container</strong></td><td>14 Nutzungen in 12 Dateien → <code><div class="ds-container ds-container-{width}"></code></td></tr><tr><td>2026-02-19</td><td><strong>ds-heading</strong></td><td>31 Nutzungen in 25 Dateien → <code><h1-h4 class="ds-heading ds-heading-h{n}"></code> mit soft/no-margin/align</td></tr><tr><td>2026-02-19</td><td><strong>ds-text</strong></td><td>80 Nutzungen in 42 Dateien → <code><p/div class="ds-text ds-text-{color} ds-text-size-{size}"></code></td></tr><tr><td>2026-02-19</td><td><strong>ds-space</strong></td><td>139 Nutzungen in 55+ Dateien → <code><div class="ds-mb-{size}"></code> / <code><div class="ds-my-{size}"></code>; neue Utility-Klassen in _ds-compat.scss</td></tr><tr><td>2026-02-19</td><td><strong>ds-flex/ds-flex-item</strong></td><td>103 Nutzungen in 29 Dateien → Plain HTML + CSS <code>@media</code> Queries; JS window.innerWidth → CSS Media Queries; <code>gap</code> statt negative-margin/padding</td></tr><tr><td>2026-02-19</td><td><strong>HTML-Validierung Bugfix</strong></td><td><code><p></code> mit Block-Level-Kindern → <code><div></code> in DateTimeRange.vue und verify.vue</td></tr><tr><td>2026-02-19</td><td><strong>Test-Fix</strong></td><td>Empty.spec.js: <code>attributes().margin</code> → <code>classes().toContain('ds-my-xxx-small')</code></td></tr><tr><td>2026-02-19</td><td><strong>Review Fixes (Session 28)</strong></td><td>~20 CodeRabbit Review-Kommentare für Tier A PR bearbeitet; Bugfixes, A11y, i18n, Scoping</td></tr><tr><td>2026-02-19</td><td><strong>Heading-Semantik</strong></td><td>ComponentSlider h1→h3, SearchHeading h1→h5 (ds-heading size-Prop wurde falsch zu h1 migriert)</td></tr><tr><td>2026-02-19</td><td><strong>A11y Fixes</strong></td><td>Label-for Mismatch (RegistrationSlideEmail), in Aside entfernt, link.id→link.url Key</td></tr><tr><td>2026-02-19</td><td><strong>i18n</strong></td><td>logout.vue "Logging out..." → $t() mit 9 Sprachen; maintenance alt-Text → $t()</td></tr><tr><td>2026-02-19</td><td><strong>CSS Fixes</strong></td><td>.buttons Klasse ergänzt (GroupForm), display:flex auf .ds-space-centered, 33%→33.333%, inline-style→Utilities</td></tr><tr><td>2026-02-19</td><td><strong>Scoping</strong></td><td>Signup.vue, maintenance/index.vue, post/create/_type.vue, post/edit/_id.vue: unscoped → scoped Style-Blöcke</td></tr><tr><td>2026-02-19</td><td><strong>Migration-Artefakte</strong></td><td>Redundante Wrapper-Divs, tote Attribute (margin="large"), Span→P Block-Level Regression</td></tr><tr><td>2026-02-20</td><td><strong>OsBadge Komponente (Session 29)</strong></td><td>Neue Komponente: CVA-Varianten (variant, size, shape), h() Render-Function, 16 Tests, 6 Stories</td></tr><tr><td>2026-02-20</td><td><strong>ds-chip → OsBadge</strong></td><td>20 Nutzungen in 5 Dateien: GroupTeaser, GroupMember, GroupForm, ContributionForm, groups/_slug</td></tr><tr><td>2026-02-20</td><td><strong>ds-tag → OsBadge</strong></td><td>3 Nutzungen in 3 Dateien: Category (shape="square"), Hashtag (shape="square"), PostTeaser (CSS)</td></tr><tr><td>2026-02-20</td><td><strong>OsBadge Features</strong></td><td>shape-Prop (pill/square), w-fit (Flex-Container-Fix), inline-flex items-center (Icon-Zentrierung)</td></tr><tr><td>2026-02-20</td><td><strong>CSS-Variable</strong></td><td>--color-default + --color-default-contrast für neutralen Badge-Hintergrund</td></tr><tr><td>2026-02-20</td><td><strong>Code-Review Fixes (Session 30)</strong></td><td>--color-default-contrast in requiredCssVariables, doppelte --color-default Deklaration entfernt</td></tr><tr><td>2026-02-20</td><td><strong>Type Safety</strong></td><td>BadgeVariant Typ exportiert, PlaygroundArgs typisiert, redundante Ternäre entfernt</td></tr><tr><td>2026-02-20</td><td><strong>Layout-Konsistenz</strong></td><td>GroupForm float:right → Flexbox align-self:flex-end, Inline-Style → Tailwind in Stories</td></tr><tr><td>2026-02-20</td><td><strong>ARIA Live Regions</strong></td><td>role="status"/aria-live="polite" auf 11 Form-Badges (WCAG 4.1.3), live Prop → Standard-Attribute</td></tr><tr><td>2026-02-20</td><td><strong>ds-grid → CSS Grid</strong></td><td>10 Dateien migriert: ds-grid/ds-grid-item → native CSS Grid + Klassen</td></tr><tr><td>2026-02-20</td><td><strong>ds-table → Plain HTML (Session 31)</strong></td><td>7 Dateien: <code><ds-table></code> → native <code><table></code> + CSS-Klassen (.ds-table, .ds-table-col, etc.)</td></tr><tr><td>2026-02-20</td><td><strong>Table-CSS</strong></td><td>_ds-compat.scss erweitert: .ds-table-wrap, .ds-table, .ds-table-col, .ds-table-head-col, bordered, condensed, alignment</td></tr><tr><td>2026-02-20</td><td><strong>fields() entfernt</strong></td><td>Computed Properties <code>fields()</code>/<code>tableFields()</code> aus 7 Dateien entfernt — Labels direkt in <code><th></code></td></tr><tr><td>2026-02-20</td><td><strong>Scope-Objekte entfernt</strong></td><td><code>scope.row</code> Zugriffe → direkte Iteration-Variable (user, tag, member, report)</td></tr><tr><td>2026-02-20</td><td><strong>OsNumber Komponente (Session 32)</strong></td><td>Neue Komponente: h() Render-Function, requestAnimationFrame Animation (1500ms ease-out), count (required), label, animated Props</td></tr><tr><td>2026-02-20</td><td><strong>ds-number + CountTo → OsNumber</strong></td><td>5 Dateien: UserTeaserPopover, TabNavigation, admin/index, profile/_slug, groups/_slug</td></tr><tr><td>2026-02-20</td><td><strong>Animation-Stabilität</strong></td><td><code>tabular-nums</code> + <code>min-width: Nch</code> für stabile Breite während Count-up Animation</td></tr><tr><td>2026-02-20</td><td><strong>CountTo.vue gelöscht</strong></td><td>vue-count-to Dependency entfernt, followedByCountStartValue/membersCountStartValue Pattern entfernt</td></tr><tr><td>2026-02-20</td><td><strong>CSS-Variable --color-text-soft</strong></td><td>Neuer Contract-Eintrag in tailwind.preset.ts + ocelot-ui-variables.scss (Label-Farbe)</td></tr><tr><td>2026-02-20</td><td><strong>Admin-Label uppercase</strong></td><td><code>.admin-stats__item .os-number-label { text-transform: uppercase }</code> per CSS statt neuem Prop</td></tr></tbody></table><hr><h2 id="_13-komponenten-katalog" tabindex="-1"><a class="header-anchor" href="#_13-komponenten-katalog"><span>13. Komponenten-Katalog</span></a></h2><blockquote><p><strong>Detaillierte Katalogisierung in separater Datei: <a class="route-link" href="/packages/ui/KATALOG.html">KATALOG.md</a></strong></p></blockquote><h3 id="zusammenfassung-aus-katalog-md" tabindex="-1"><a class="header-anchor" href="#zusammenfassung-aus-katalog-md"><span>Zusammenfassung (aus KATALOG.md)</span></a></h3><table><thead><tr><th>Quelle</th><th>Gesamt</th><th>Status</th></tr></thead><tbody><tr><td>Webapp</td><td>139</td><td>✅ Katalogisiert</td></tr><tr><td>Styleguide</td><td>38</td><td>✅ Katalogisiert — 23 in Webapp genutzt</td></tr></tbody></table><p><strong>Styleguide-Migration:</strong></p><table><thead><tr><th>Status</th><th>Komponenten</th></tr></thead><tbody><tr><td>✅ UI-Library</td><td>OsButton, OsIcon, OsSpinner, OsCard, OsBadge, OsNumber (6)</td></tr><tr><td>✅ → Plain HTML</td><td>Section, Placeholder, List, ListItem, Container, Heading, Text, Space, Flex, FlexItem, Grid, GridItem, Table (13) — Tier A/B</td></tr><tr><td>✅ → UI-Library</td><td>Chip, Tag → OsBadge (2), Number → OsNumber (1) — Tier B</td></tr><tr><td>⬜ → Plain HTML</td><td>Radio (1) — Tier B</td></tr><tr><td>⬜ → UI-Library</td><td>Modal, Input, Menu, MenuItem, Select (5) — Tier 2-3</td></tr><tr><td>⬜ Nicht genutzt</td><td>Code, CopyField, FormItem, InputError, InputLabel, Page, PageTitle, Logo, Avatar, TableCol, TableHeadCol (11)</td></tr><tr><td>⬜ Offen</td><td>Form (18 Dateien — HTML <code><form></code> oder OsForm?)</td></tr></tbody></table><hr><h2 id="_14-ressourcen-links" tabindex="-1"><a class="header-anchor" href="#_14-ressourcen-links"><span>14. Ressourcen & Links</span></a></h2><p><strong>Projekt:</strong></p><ul><li><a href="https://github.com/Ocelot-Social-Community/Ocelot-Social" target="_blank" rel="noopener noreferrer">Ocelot-Social Repository</a></li><li><a href="http://styleguide.ocelot.social/" target="_blank" rel="noopener noreferrer">Styleguide (Live)</a></li><li>Bestehender Styleguide Code: <code>../../styleguide/</code></li><li>Bestehende Webapp: <code>../../webapp/</code></li><li>Design Tokens: <code>../../styleguide/src/system/tokens/</code></li></ul><p><strong>Tracking:</strong></p><ul><li>Katalogisierung: <code>./KATALOG.md</code></li><li>Komponenten-Status: <code>./src/components/*/STATUS.md</code> (colocated)</li></ul><p><strong>Dokumentation:</strong></p><ul><li><a href="https://vuejs.org/" target="_blank" rel="noopener noreferrer">Vue 3</a></li><li><a href="https://vitejs.dev/" target="_blank" rel="noopener noreferrer">Vite</a></li><li><a href="https://tailwindcss.com/" target="_blank" rel="noopener noreferrer">Tailwind CSS</a></li><li><a href="https://storybook.js.org/" target="_blank" rel="noopener noreferrer">Storybook</a></li><li><a href="https://vitest.dev/" target="_blank" rel="noopener noreferrer">Vitest</a></li><li><a href="https://github.com/vueuse/vue-demi" target="_blank" rel="noopener noreferrer">vue-demi</a></li></ul><hr><h2 id="_15-dokumentationsstrategie-details" tabindex="-1"><a class="header-anchor" href="#_15-dokumentationsstrategie-details"><span>15. Dokumentationsstrategie (Details)</span></a></h2><h3 id="ubersicht" tabindex="-1"><a class="header-anchor" href="#ubersicht"><span>Übersicht</span></a></h3><div class="language- line-numbers-mode" data-highlighter="shiki" data-ext="" data-title="" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span>┌─────────────────────────────────────────────────────────────┐</span></span>
|
||
<span class="line"><span>│ GENERIERT (aus Code) - Single Source of Truth │</span></span>
|
||
<span class="line"><span>├─────────────────────────────────────────────────────────────┤</span></span>
|
||
<span class="line"><span>│ • README.md Komponenten-Tabelle │</span></span>
|
||
<span class="line"><span>│ • Props/Events/Slots Dokumentation │</span></span>
|
||
<span class="line"><span>│ • TypeScript-Typen │</span></span>
|
||
<span class="line"><span>└─────────────────────────────────────────────────────────────┘</span></span>
|
||
<span class="line"><span> +</span></span>
|
||
<span class="line"><span>┌─────────────────────────────────────────────────────────────┐</span></span>
|
||
<span class="line"><span>│ MANUELL │</span></span>
|
||
<span class="line"><span>├─────────────────────────────────────────────────────────────┤</span></span>
|
||
<span class="line"><span>│ • README.md Installation, Quick Start, Theming │</span></span>
|
||
<span class="line"><span>│ • Storybook Stories (interaktive Beispiele) │</span></span>
|
||
<span class="line"><span>│ • ARCHITECTURE.md (Entscheidungen) │</span></span>
|
||
<span class="line"><span>│ • Best Practices, Patterns │</span></span>
|
||
<span class="line"><span>└─────────────────────────────────────────────────────────────┘</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="readme-md-struktur" tabindex="-1"><a class="header-anchor" href="#readme-md-struktur"><span>README.md Struktur</span></a></h3><div class="language-markdown line-numbers-mode" data-highlighter="shiki" data-ext="markdown" data-title="markdown" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"># @ocelot-social/ui</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">## Übersicht</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;">-</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> Was ist die Library?</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;">-</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> Link zu Storybook (Live-Dokumentation)</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">## Installation</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;">-</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> npm install</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;">-</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> Peer Dependencies (vue, vue-demi)</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">## Quick Start</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;">-</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> Minimal-Beispiel (Import + Nutzung)</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;">-</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> Mit Tailwind / Ohne Tailwind</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">## Theming / Branding</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;">-</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> CSS Custom Properties überschreiben</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;">-</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> Beispiel-Branding</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">## Icon-System</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;">-</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> System-Icons (enthalten)</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;">-</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> Eigene Icons registrieren</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">## Vue 2 / Vue 3</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;">-</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> vue-demi Erklärung</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;">-</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> Kompatibilitätshinweise</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">## Komponenten ← GENERIERT</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;">-</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> Tabelle aller Komponenten</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;">-</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> Link zu Storybook für Details</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">## Contributing</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;">-</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> Link zu CONTRIBUTING.md</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">## License</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;">-</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> Apache 2.0</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="generierung-mit-vue-component-meta" tabindex="-1"><a class="header-anchor" href="#generierung-mit-vue-component-meta"><span>Generierung mit vue-component-meta</span></a></h3><p><strong>Tool:</strong> <code>vue-component-meta</code> (offizielles Vue-Tool)</p><div class="language-typescript line-numbers-mode" data-highlighter="shiki" data-ext="typescript" data-title="typescript" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;">// scripts/generate-docs.ts</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">createComponentMetaChecker</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> 'vue-component-meta'</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;">// Extrahiert aus .vue Dateien:</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">{</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;"> name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">"OsButton"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">,</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;"> description</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">"Primary button component for user actions"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">,</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;"> props</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: [</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> {</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> name</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> "variant"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">,</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> type</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> "'primary' | 'secondary' | 'danger'"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">,</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> default</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> "'primary'"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">,</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> required</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66;"> false</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">,</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> description</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> "Visual style variant"</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> }</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> ],</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;"> events</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: [{ </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">name</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> "click"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">type</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> "(event: MouseEvent) => void"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> }],</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;"> slots</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: [{ </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">name</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> "default"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">description</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> "Button content"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> }]</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">}</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>Generierte Komponenten-Tabelle:</strong></p><div class="language-markdown line-numbers-mode" data-highlighter="shiki" data-ext="markdown" data-title="markdown" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">| Komponente | Beschreibung | Props | Events | Slots |</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">|------------|--------------|-------|--------|-------|</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">| OsButton | Primary button for actions | 8 | 1 | 2 |</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">| OsCard | Container with header/footer | 5 | 0 | 3 |</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">| ... | ... | ... | ... | ... |</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="ci-workflow-docs-check" tabindex="-1"><a class="header-anchor" href="#ci-workflow-docs-check"><span>CI-Workflow: docs-check</span></a></h3><div class="language-yaml line-numbers-mode" data-highlighter="shiki" data-ext="yaml" data-title="yaml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;"># .github/workflows/docs-check.yml</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">Documentation Check</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66;">on</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: [</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">push</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">pull_request</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">]</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">jobs</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">:</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> docs</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">:</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> runs-on</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">ubuntu-latest</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> steps</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">:</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">uses</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">actions/checkout@v4</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">Generate docs from components</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> run</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">npm run docs:generate</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">Check for undocumented props</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> run</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">npm run docs:check-coverage</span></span>
|
||
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;"> # Fails if props/events/slots missing JSDoc</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">Verify README is up-to-date</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> run</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">npm run docs:verify</span></span>
|
||
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;"> # Compares generated table with README</span></span>
|
||
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;"> # Fails if differences found</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>Scripts in package.json:</strong></p><div class="language-json line-numbers-mode" data-highlighter="shiki" data-ext="json" data-title="json" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">{</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> "scripts"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: {</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> "docs:generate"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">"tsx scripts/generate-docs.ts"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">,</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> "docs:check-coverage"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">"tsx scripts/check-doc-coverage.ts"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">,</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> "docs:verify"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">"tsx scripts/verify-readme.ts"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">,</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> "docs:update"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">"npm run docs:generate && npm run docs:inject-readme"</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> }</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">}</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="eslint-regeln-fur-dokumentation" tabindex="-1"><a class="header-anchor" href="#eslint-regeln-fur-dokumentation"><span>ESLint-Regeln für Dokumentation</span></a></h3><div class="language-javascript line-numbers-mode" data-highlighter="shiki" data-ext="javascript" data-title="javascript" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;">// eslint.config.js</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">{</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;"> rules</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: {</span></span>
|
||
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;"> // Erzwingt JSDoc für exportierte Funktionen/Komponenten</span></span>
|
||
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> "jsdoc/require-jsdoc"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: [</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">"error"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, {</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> require</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> {</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> FunctionDeclaration</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66;"> true</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">,</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> ClassDeclaration</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66;"> true</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> }</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> }],</span></span>
|
||
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> "jsdoc/require-description"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">"error"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">,</span></span>
|
||
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> "jsdoc/require-param-description"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">"error"</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> }</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">}</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="dokumentations-phasen" tabindex="-1"><a class="header-anchor" href="#dokumentations-phasen"><span>Dokumentations-Phasen</span></a></h3><table><thead><tr><th>Phase</th><th>Dokumentation</th><th>Status</th></tr></thead><tbody><tr><td><strong>Phase 2</strong></td><td>README.md Grundgerüst (Installation, Setup)</td><td>Manuell</td></tr><tr><td><strong>Phase 2</strong></td><td>CONTRIBUTING.md</td><td>Manuell</td></tr><tr><td><strong>Phase 2</strong></td><td>docs:generate Script einrichten</td><td>Automatisiert</td></tr><tr><td><strong>Phase 2</strong></td><td>CI docs-check Workflow</td><td>Automatisiert</td></tr><tr><td><strong>Phase 4</strong></td><td>Storybook Stories pro Komponente</td><td>Manuell</td></tr><tr><td><strong>Phase 4</strong></td><td>JSDoc in Komponenten</td><td>Im Code</td></tr><tr><td><strong>Phase 4</strong></td><td>README Komponenten-Tabelle</td><td>Generiert</td></tr><tr><td><strong>Phase 4</strong></td><td>README Finalisierung</td><td>Manuell</td></tr><tr><td><strong>Phase 5</strong></td><td>ARCHITECTURE.md (aus PROJEKT.md)</td><td>Manuell</td></tr></tbody></table><h3 id="nach-der-migration-datei-transformation" tabindex="-1"><a class="header-anchor" href="#nach-der-migration-datei-transformation"><span>Nach der Migration: Datei-Transformation</span></a></h3><div class="language- line-numbers-mode" data-highlighter="shiki" data-ext="" data-title="" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span>Während Migration:</span></span>
|
||
<span class="line"><span>├── PROJEKT.md # Planungs- und Statusdokument</span></span>
|
||
<span class="line"><span>├── KATALOG.md # Komponenten-Tracking</span></span>
|
||
<span class="line"><span></span></span>
|
||
<span class="line"><span>Nach Phase 5 (Migration abgeschlossen):</span></span>
|
||
<span class="line"><span>├── README.md # Nutzer-Dokumentation (teilweise generiert)</span></span>
|
||
<span class="line"><span>├── CONTRIBUTING.md # Beitragende</span></span>
|
||
<span class="line"><span>├── CHANGELOG.md # Automatisch via release-please</span></span>
|
||
<span class="line"><span>├── ARCHITECTURE.md # Entscheidungen aus PROJEKT.md §2, §4, §5, §11, §15, §16</span></span>
|
||
<span class="line"><span>├── src/components/*/STATUS.md # Komponenten-Status (colocated, werden beibehalten)</span></span>
|
||
<span class="line"><span>└── docs/</span></span>
|
||
<span class="line"><span> └── archive/ # Historische Referenz (optional)</span></span>
|
||
<span class="line"><span> ├── PROJEKT.md</span></span>
|
||
<span class="line"><span> └── KATALOG.md</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>Was wird übernommen nach ARCHITECTURE.md:</strong></p><ul><li>§2 Tech-Stack (gekürzt)</li><li>§5 Design-Token & Branding (Token-Architektur)</li><li>§11 Entscheidungen (relevante, nicht Prozess-bezogene)</li><li>§4 Icon-Architektur</li><li>§9/§15 Dokumentationsstrategie (gekürzt)</li><li>§16 Library vs. Webapp</li></ul><p><strong>Was wird archiviert/gelöscht:</strong></p><ul><li>Fortschritt → erledigt</li><li>Aktueller Stand → erledigt</li><li>Meilensteine → erledigt</li><li>§12 Arbeitsprotokoll → historisch interessant, archivieren</li><li>KATALOG.md → nur für Migration relevant, archivieren</li></ul><hr><h1 id="abgrenzungen" tabindex="-1"><a class="header-anchor" href="#abgrenzungen"><span>ABGRENZUNGEN</span></a></h1><h2 id="_16-library-vs-webapp" tabindex="-1"><a class="header-anchor" href="#_16-library-vs-webapp"><span>16. Library vs. Webapp</span></a></h2><h3 id="grundprinzip" tabindex="-1"><a class="header-anchor" href="#grundprinzip"><span>Grundprinzip</span></a></h3><div class="language- line-numbers-mode" data-highlighter="shiki" data-ext="" data-title="" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span>┌─────────────────────────────────────────────────────────────┐</span></span>
|
||
<span class="line"><span>│ @ocelot-social/ui (Library) │</span></span>
|
||
<span class="line"><span>├─────────────────────────────────────────────────────────────┤</span></span>
|
||
<span class="line"><span>│ • Rein präsentational │</span></span>
|
||
<span class="line"><span>│ • Keine Business-Logik │</span></span>
|
||
<span class="line"><span>│ • Keine API-Calls │</span></span>
|
||
<span class="line"><span>│ • Kein App-State │</span></span>
|
||
<span class="line"><span>│ • Wiederverwendbar in jedem Vue-Projekt │</span></span>
|
||
<span class="line"><span>└─────────────────────────────────────────────────────────────┘</span></span>
|
||
<span class="line"><span> ▲</span></span>
|
||
<span class="line"><span> │ nutzt</span></span>
|
||
<span class="line"><span>┌─────────────────────────────────────────────────────────────┐</span></span>
|
||
<span class="line"><span>│ Webapp (Ocelot) │</span></span>
|
||
<span class="line"><span>├─────────────────────────────────────────────────────────────┤</span></span>
|
||
<span class="line"><span>│ • Business-Logik (GraphQL, Auth, etc.) │</span></span>
|
||
<span class="line"><span>│ • App-State (Vuex/Pinia) │</span></span>
|
||
<span class="line"><span>│ • i18n Texte │</span></span>
|
||
<span class="line"><span>│ • Routing-Logik │</span></span>
|
||
<span class="line"><span>│ • Ocelot-spezifische Features │</span></span>
|
||
<span class="line"><span>└─────────────────────────────────────────────────────────────┘</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="entscheidungs-checkliste" tabindex="-1"><a class="header-anchor" href="#entscheidungs-checkliste"><span>Entscheidungs-Checkliste</span></a></h3><table><thead><tr><th>Kriterium</th><th>Library ✅</th><th>Webapp ✅</th></tr></thead><tbody><tr><td><strong>Business-Logik</strong></td><td>Keine</td><td>Hat GraphQL/API-Calls</td></tr><tr><td><strong>App-State</strong></td><td>Kein Vuex/Pinia</td><td>Braucht Store</td></tr><tr><td><strong>i18n</strong></td><td>Nur via Props</td><td>Nutzt <code>$t()</code> direkt</td></tr><tr><td><strong>Routing</strong></td><td>Nur via Props (<code>to</code>)</td><td>Nutzt <code>$router</code> direkt</td></tr><tr><td><strong>Wiederverwendbar</strong></td><td>In jedem Vue-Projekt</td><td>Nur in Ocelot</td></tr><tr><td><strong>Abhängigkeiten</strong></td><td>Nur Vue + vue-demi</td><td>Ocelot-spezifisch</td></tr><tr><td><strong>Styling</strong></td><td>Design-Tokens</td><td>App-spezifische Styles</td></tr><tr><td><strong>Daten</strong></td><td>Erhält via Props</td><td>Fetcht selbst</td></tr></tbody></table><h3 id="entscheidungsbaum" tabindex="-1"><a class="header-anchor" href="#entscheidungsbaum"><span>Entscheidungsbaum</span></a></h3><div class="language- line-numbers-mode" data-highlighter="shiki" data-ext="" data-title="" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span>Komponente X</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>┌─────────────────────────────────────┐</span></span>
|
||
<span class="line"><span>│ Hat sie Business-Logik? │</span></span>
|
||
<span class="line"><span>│ (API-Calls, Mutations, Auth-Check) │</span></span>
|
||
<span class="line"><span>└─────────────────────────────────────┘</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ├── JA ──► WEBAPP</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼ NEIN</span></span>
|
||
<span class="line"><span>┌─────────────────────────────────────┐</span></span>
|
||
<span class="line"><span>│ Braucht sie App-State? │</span></span>
|
||
<span class="line"><span>│ (Vuex, Pinia, globaler State) │</span></span>
|
||
<span class="line"><span>└─────────────────────────────────────┘</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ├── JA ──► WEBAPP</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼ NEIN</span></span>
|
||
<span class="line"><span>┌─────────────────────────────────────┐</span></span>
|
||
<span class="line"><span>│ Nutzt sie $t() oder $router direkt? │</span></span>
|
||
<span class="line"><span>└─────────────────────────────────────┘</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ├── JA ──► WEBAPP (oder refactoren)</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼ NEIN</span></span>
|
||
<span class="line"><span>┌─────────────────────────────────────┐</span></span>
|
||
<span class="line"><span>│ Ist sie generisch wiederverwendbar? │</span></span>
|
||
<span class="line"><span>│ (Könnte in anderem Projekt helfen) │</span></span>
|
||
<span class="line"><span>└─────────────────────────────────────┘</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ├── NEIN ──► WEBAPP</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼ JA</span></span>
|
||
<span class="line"><span></span></span>
|
||
<span class="line"><span> ══► LIBRARY</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="quantitative-regel" tabindex="-1"><a class="header-anchor" href="#quantitative-regel"><span>Quantitative Regel</span></a></h3><blockquote><p><strong>Wenn ≥2 Kriterien auf "Webapp" zeigen → Webapp</strong><strong>Wenn alle Kriterien auf "Library" zeigen → Library</strong></p></blockquote><h3 id="konkrete-beispiele" tabindex="-1"><a class="header-anchor" href="#konkrete-beispiele"><span>Konkrete Beispiele</span></a></h3><table><thead><tr><th>Komponente</th><th>Entscheidung</th><th>Begründung</th></tr></thead><tbody><tr><td><code>OsButton</code></td><td><strong>Library</strong></td><td>Rein präsentational, keine Logik</td></tr><tr><td><code>OsModal</code></td><td><strong>Library</strong></td><td>UI-Container, Logik via Events</td></tr><tr><td><code>OsInput</code></td><td><strong>Library</strong></td><td>Generisches Form-Element</td></tr><tr><td><code>OsAvatar</code></td><td><strong>Library</strong></td><td>Nur Bild + Fallback-Initialen</td></tr><tr><td><code>OsCard</code></td><td><strong>Library</strong></td><td>Layout-Container</td></tr><tr><td><code>OsDropdown</code></td><td><strong>Library</strong></td><td>Popover-Mechanik</td></tr><tr><td><code>FollowButton</code></td><td><strong>Webapp</strong></td><td>GraphQL Mutation, User-State</td></tr><tr><td><code>PostTeaser</code></td><td><strong>Webapp</strong></td><td>Ocelot-Datenstruktur, Links</td></tr><tr><td><code>CommentForm</code></td><td><strong>Webapp</strong></td><td>API-Call, Auth-Check</td></tr><tr><td><code>ConfirmModal</code></td><td><strong>Webapp</strong></td><td>Nutzt OsModal + Callbacks</td></tr><tr><td><code>LoginForm</code></td><td><strong>Webapp</strong></td><td>Auth-Logik, Routing, i18n</td></tr><tr><td><code>UserAvatar</code></td><td><strong>Webapp</strong></td><td>Nutzt OsAvatar + User-Daten</td></tr><tr><td><code>NotificationMenu</code></td><td><strong>Webapp</strong></td><td>GraphQL, Store, i18n</td></tr></tbody></table><h3 id="composition-pattern-fur-grenzfalle" tabindex="-1"><a class="header-anchor" href="#composition-pattern-fur-grenzfalle"><span>Composition Pattern für Grenzfälle</span></a></h3><p>Wenn eine Komponente UI- und Business-Anteile hat: <strong>Aufteilen</strong></p><div class="language- line-numbers-mode" data-highlighter="shiki" data-ext="" data-title="" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span>┌─────────────────────────────────────────────────────────────┐</span></span>
|
||
<span class="line"><span>│ LIBRARY: OsModal │</span></span>
|
||
<span class="line"><span>│ - Overlay, Animation, Backdrop │</span></span>
|
||
<span class="line"><span>│ - close/confirm Events │</span></span>
|
||
<span class="line"><span>│ - Slots für Content │</span></span>
|
||
<span class="line"><span>│ - Props: title, confirmLabel, cancelLabel │</span></span>
|
||
<span class="line"><span>└─────────────────────────────────────────────────────────────┘</span></span>
|
||
<span class="line"><span> ▲</span></span>
|
||
<span class="line"><span> │ nutzt</span></span>
|
||
<span class="line"><span>┌─────────────────────────────────────────────────────────────┐</span></span>
|
||
<span class="line"><span>│ WEBAPP: DeleteUserModal │</span></span>
|
||
<span class="line"><span>│ - Wraps OsModal │</span></span>
|
||
<span class="line"><span>│ - GraphQL Mutation │</span></span>
|
||
<span class="line"><span>│ - i18n Texte via $t() │</span></span>
|
||
<span class="line"><span>│ - Redirect nach Löschung │</span></span>
|
||
<span class="line"><span>└─────────────────────────────────────────────────────────────┘</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>Beispiel-Code:</strong></p><div class="language-vue line-numbers-mode" data-highlighter="shiki" data-ext="vue" data-title="vue" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;"><!-- Webapp: DeleteUserModal.vue --></span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"><</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">template</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">></span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> <</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">OsModal</span></span>
|
||
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66;"> :title</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">"$t('user.delete.title')"</span></span>
|
||
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66;"> :confirm-label</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">"$t('common.delete')"</span></span>
|
||
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66;"> confirm-variant</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">"danger"</span></span>
|
||
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66;"> @confirm</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">"handleDelete"</span></span>
|
||
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66;"> @cancel</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">"$emit('close')"</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> ></span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> <</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">p</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">>{{ $t('user.delete.warning', { name: user.name }) }}</</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">p</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">></span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> </</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">OsModal</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">></span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"></</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">template</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">></span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"><</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">script</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66;"> setup</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">></span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">OsModal</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> '@ocelot-social/ui'</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">useDeleteUserMutation</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> '~/graphql/mutations'</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">useRouter</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> 'vue-router'</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B;"> props</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;"> =</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;"> defineProps</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">([</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">'user'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">])</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B;"> emit</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;"> =</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;"> defineEmits</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">([</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">'close'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">])</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B;"> router</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;"> =</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;"> useRouter</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">()</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">const</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">mutate</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B;">deleteUser</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> } </span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;">=</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;"> useDeleteUserMutation</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">()</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">const</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;"> handleDelete</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;"> =</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;"> async</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> () </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">=></span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> {</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;"> await</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;"> deleteUser</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">({ </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">id</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;"> props</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B;">user</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> })</span></span>
|
||
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;"> emit</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">'close'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">)</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;"> router</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;">push</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">'/'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">)</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">}</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"></</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">script</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">></span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="checkliste-bei-neuer-komponente" tabindex="-1"><a class="header-anchor" href="#checkliste-bei-neuer-komponente"><span>Checkliste bei neuer Komponente</span></a></h3><p>Vor dem Erstellen einer Komponente diese Fragen beantworten:</p><div class="language- line-numbers-mode" data-highlighter="shiki" data-ext="" data-title="" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span>[ ] Wo gehört die Komponente hin? (Entscheidungsbaum durchlaufen)</span></span>
|
||
<span class="line"><span>[ ] Falls Library: Sind alle Texte via Props?</span></span>
|
||
<span class="line"><span>[ ] Falls Library: Keine direkten Store/Router Imports?</span></span>
|
||
<span class="line"><span>[ ] Falls Webapp: Welche Library-Komponenten werden genutzt?</span></span>
|
||
<span class="line"><span>[ ] Falls Grenzfall: Kann sie aufgeteilt werden?</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><hr><h2 id="_16a-webapp-↔-maintenance-code-sharing" tabindex="-1"><a class="header-anchor" href="#_16a-webapp-↔-maintenance-code-sharing"><span>16a. Webapp ↔ Maintenance Code-Sharing</span></a></h2><h3 id="problemstellung" tabindex="-1"><a class="header-anchor" href="#problemstellung"><span>Problemstellung</span></a></h3><p>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.</p><p><strong>Das DX-Problem:</strong> "shared" hat kein logisches Kriterium außer "wird in beiden gebraucht".</p><h3 id="analysierte-optionen" tabindex="-1"><a class="header-anchor" href="#analysierte-optionen"><span>Analysierte Optionen</span></a></h3><table><thead><tr><th>Option</th><th>Beschreibung</th><th>Bewertung</th></tr></thead><tbody><tr><td><strong>A: Domain Packages</strong></td><td><code>@ocelot-social/auth</code>, <code>@ocelot-social/posts</code>, etc.</td><td>Gut bei vielen Komponenten, aber Overhead</td></tr><tr><td><strong>B: Core + Duplikation</strong></td><td>Composables teilen, Komponenten duplizieren</td><td>Gut wenn UI unterschiedlich</td></tr><tr><td><strong>C: Webapp als Source</strong></td><td>Maintenance importiert aus Webapp</td><td>Einfachste Lösung</td></tr></tbody></table><h3 id="empfehlung-option-c-webapp-als-source-of-truth" tabindex="-1"><a class="header-anchor" href="#empfehlung-option-c-webapp-als-source-of-truth"><span>Empfehlung: Option C (Webapp als Source of Truth)</span></a></h3><div class="language- line-numbers-mode" data-highlighter="shiki" data-ext="" data-title="" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span>┌─────────────────────────────────────────────────────────────┐</span></span>
|
||
<span class="line"><span>│ @ocelot-social/ui │</span></span>
|
||
<span class="line"><span>│ ───────────────── │</span></span>
|
||
<span class="line"><span>│ • OsButton, OsModal, OsCard, OsInput │</span></span>
|
||
<span class="line"><span>│ • Rein präsentational, keine Abhängigkeiten │</span></span>
|
||
<span class="line"><span>├─────────────────────────────────────────────────────────────┤</span></span>
|
||
<span class="line"><span>│ webapp/ │</span></span>
|
||
<span class="line"><span>│ ─────── │</span></span>
|
||
<span class="line"><span>│ • Alle Business-Komponenten (Source of Truth) │</span></span>
|
||
<span class="line"><span>│ • Composables in webapp/lib/composables/ │</span></span>
|
||
<span class="line"><span>│ • GraphQL in webapp/graphql/ │</span></span>
|
||
<span class="line"><span>│ • Ist die "Haupt-App" │</span></span>
|
||
<span class="line"><span>├─────────────────────────────────────────────────────────────┤</span></span>
|
||
<span class="line"><span>│ maintenance/ │</span></span>
|
||
<span class="line"><span>│ ──────────── │</span></span>
|
||
<span class="line"><span>│ • Importiert aus @ocelot-social/ui │</span></span>
|
||
<span class="line"><span>│ • Importiert aus webapp/ via Alias │</span></span>
|
||
<span class="line"><span>│ • Nur maintenance-spezifische Komponenten lokal │</span></span>
|
||
<span class="line"><span>└─────────────────────────────────────────────────────────────┘</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="umsetzung" tabindex="-1"><a class="header-anchor" href="#umsetzung"><span>Umsetzung</span></a></h3><p><strong>maintenance/nuxt.config.js:</strong></p><div class="language-javascript line-numbers-mode" data-highlighter="shiki" data-ext="javascript" data-title="javascript" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">export</span><span style="--shiki-light:#E45649;--shiki-dark:#C678DD;"> default</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> {</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> alias</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> {</span></span>
|
||
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> '@webapp'</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> '../webapp'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">,</span></span>
|
||
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> '@ocelot-social/ui'</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> '../packages/ui/dist'</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> }</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">}</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>Import in Maintenance:</strong></p><div class="language-typescript line-numbers-mode" data-highlighter="shiki" data-ext="typescript" data-title="typescript" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;">// UI-Komponenten aus Library</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">OsButton</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">OsModal</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> '@ocelot-social/ui'</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;">// Business-Komponenten aus Webapp</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">import</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> FollowButton</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;"> from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> '@webapp/components/FollowButton.vue'</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">import</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> PostTeaser</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;"> from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> '@webapp/components/PostTeaser.vue'</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;">// Composables aus Webapp</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">useAuth</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> '@webapp/lib/composables/useAuth'</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">useFollow</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> '@webapp/lib/composables/useFollow'</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="kriterien-fur-entwickler" tabindex="-1"><a class="header-anchor" href="#kriterien-fur-entwickler"><span>Kriterien für Entwickler</span></a></h3><table><thead><tr><th>Frage</th><th>Antwort</th></tr></thead><tbody><tr><td>Wo suche ich eine UI-Komponente?</td><td><code>@ocelot-social/ui</code></td></tr><tr><td>Wo suche ich eine Business-Komponente?</td><td><code>webapp/components/</code></td></tr><tr><td>Wo erstelle ich eine neue geteilte Komponente?</td><td><code>webapp/components/</code></td></tr><tr><td>Wo erstelle ich maintenance-spezifische Komponenten?</td><td><code>maintenance/components/</code></td></tr></tbody></table><h3 id="vorteile" tabindex="-1"><a class="header-anchor" href="#vorteile"><span>Vorteile</span></a></h3><ol><li><strong>Klare Regel:</strong> Alles Business-bezogene ist in Webapp</li><li><strong>Kein neues Package:</strong> Weniger Overhead</li><li><strong>Eine Source of Truth:</strong> Keine Sync-Probleme</li><li><strong>Einfache Migration:</strong> Später ggf. Domain-Packages extrahieren</li></ol><h3 id="spatere-evolution-optional" tabindex="-1"><a class="header-anchor" href="#spatere-evolution-optional"><span>Spätere Evolution (optional)</span></a></h3><p>Wenn klare Patterns entstehen, können Domain-Packages extrahiert werden:</p><div class="language- line-numbers-mode" data-highlighter="shiki" data-ext="" data-title="" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span>Phase 1 (jetzt): Webapp ist Source of Truth</span></span>
|
||
<span class="line"><span>Phase 2 (später): Patterns identifizieren</span></span>
|
||
<span class="line"><span>Phase 3 (später): @ocelot-social/auth, @ocelot-social/posts, etc.</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="entscheidung" tabindex="-1"><a class="header-anchor" href="#entscheidung"><span>Entscheidung</span></a></h3><table><thead><tr><th>#</th><th>Datum</th><th>Entscheidung</th></tr></thead><tbody><tr><td>72</td><td>2026-02-09</td><td>Webapp als Source of Truth für geteilte Business-Komponenten</td></tr></tbody></table><hr><h2 id="_16b-daten-entkopplung-viewmodel-mapper-pattern" tabindex="-1"><a class="header-anchor" href="#_16b-daten-entkopplung-viewmodel-mapper-pattern"><span>16b. Daten-Entkopplung (ViewModel/Mapper Pattern)</span></a></h2><h3 id="problemstellung-1" tabindex="-1"><a class="header-anchor" href="#problemstellung-1"><span>Problemstellung</span></a></h3><p>Komponenten sind oft direkt an API/GraphQL-Strukturen gekoppelt:</p><div class="language-vue line-numbers-mode" data-highlighter="shiki" data-ext="vue" data-title="vue" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;"><!-- ❌ Tight Coupling --></span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"><</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">UserCard</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> :</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66;">user</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">"</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;">graphqlResponse</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">User</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> /></span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">// Komponente kennt GraphQL-Struktur</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">props.user.avatar.url</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">props.user._followedByCurrentUserCount // Underscore?!</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">props.user.__typename // Leaked!</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>Probleme:</strong></p><ul><li>Schema-Änderung = alle Komponenten anpassen</li><li><code>__typename</code>, <code>_count</code> etc. leaken in die UI</li><li>Schwer testbar (braucht echte GraphQL-Struktur)</li><li>Komponenten nicht wiederverwendbar</li></ul><h3 id="losung-viewmodel-mapper-pattern" tabindex="-1"><a class="header-anchor" href="#losung-viewmodel-mapper-pattern"><span>Lösung: ViewModel + Mapper Pattern</span></a></h3><div class="language- line-numbers-mode" data-highlighter="shiki" data-ext="" data-title="" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span>┌─────────────────────────────────────────────────────────────┐</span></span>
|
||
<span class="line"><span>│ GraphQL / API Layer │</span></span>
|
||
<span class="line"><span>│ • Queries & Mutations │</span></span>
|
||
<span class="line"><span>│ • Generated Types (graphql-codegen) │</span></span>
|
||
<span class="line"><span>└─────────────────────────────────────────────────────────────┘</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>┌─────────────────────────────────────────────────────────────┐</span></span>
|
||
<span class="line"><span>│ Mappers (einziger Ort der API-Struktur kennt) │</span></span>
|
||
<span class="line"><span>│ • toUserCardViewModel(graphqlUser) → UserCardViewModel │</span></span>
|
||
<span class="line"><span>│ • toPostTeaserViewModel(graphqlPost) → PostTeaserViewModel │</span></span>
|
||
<span class="line"><span>└─────────────────────────────────────────────────────────────┘</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>┌─────────────────────────────────────────────────────────────┐</span></span>
|
||
<span class="line"><span>│ ViewModels (was die UI braucht) │</span></span>
|
||
<span class="line"><span>│ • UserCardViewModel { displayName, avatarUrl, ... } │</span></span>
|
||
<span class="line"><span>│ • PostTeaserViewModel { title, excerpt, ... } │</span></span>
|
||
<span class="line"><span>└─────────────────────────────────────────────────────────────┘</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>┌─────────────────────────────────────────────────────────────┐</span></span>
|
||
<span class="line"><span>│ Presentational Components (kennen nur ViewModels) │</span></span>
|
||
<span class="line"><span>│ • <UserCard :user="UserCardViewModel" /> │</span></span>
|
||
<span class="line"><span>│ • <PostTeaser :post="PostTeaserViewModel" /> │</span></span>
|
||
<span class="line"><span>└─────────────────────────────────────────────────────────────┘</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="implementierung" tabindex="-1"><a class="header-anchor" href="#implementierung"><span>Implementierung</span></a></h3><p><strong>1. ViewModels definieren:</strong></p><div class="language-typescript line-numbers-mode" data-highlighter="shiki" data-ext="typescript" data-title="typescript" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;">// types/viewModels.ts</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">export</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;"> interface</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B;"> UserCardViewModel</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> {</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;"> id</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B;"> string</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;"> displayName</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B;"> string</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;"> avatarUrl</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B;"> string</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;"> |</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B;"> null</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;"> followerCount</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B;"> number</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;"> isFollowedByMe</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B;"> boolean</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">}</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">export</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;"> interface</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B;"> PostTeaserViewModel</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> {</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;"> id</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B;"> string</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;"> title</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B;"> string</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;"> excerpt</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B;"> string</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;"> authorName</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B;"> string</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;"> authorAvatarUrl</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B;"> string</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;"> |</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B;"> null</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;"> createdAt</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B;"> Date</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;"> commentCount</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B;"> number</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;"> canEdit</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B;"> boolean</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">}</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>2. Mapper-Funktionen:</strong></p><div class="language-typescript line-numbers-mode" data-highlighter="shiki" data-ext="typescript" data-title="typescript" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;">// mappers/userMapper.ts</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">import</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;"> type</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">UserCardViewModel</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> '~/types/viewModels'</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">import</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;"> type</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">UserGraphQL</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> '~/graphql/types'</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">export</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;"> function</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;"> toUserCardViewModel</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic;"> user</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B;"> UserGraphQL</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">,</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic;"> currentUserId</span><span style="--shiki-light:#0184BC;--shiki-dark:#C678DD;">?</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B;"> string</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">)</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B;"> UserCardViewModel</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> {</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;"> return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> {</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> id</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;"> user</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">,</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> displayName</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;"> user</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">name</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;"> ||</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;"> user</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">slug</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;"> ||</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> 'Anonymous'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">,</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> avatarUrl</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;"> user</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B;">avatar</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">?.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">url</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;"> ??</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66;"> null</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">,</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> followerCount</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;"> user</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">_followedByCurrentUserCount</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;"> ??</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66;"> 0</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">,</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> isFollowedByMe</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;"> user</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">followedByCurrentUser</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;"> ??</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66;"> false</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">,</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> }</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">}</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>3. Komponenten nutzen nur ViewModels:</strong></p><div class="language-vue line-numbers-mode" data-highlighter="shiki" data-ext="vue" data-title="vue" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;"><!-- components/UserCard.vue --></span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"><</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">script</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66;"> setup</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66;"> lang</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">"ts"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">></span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">import</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;"> type</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">UserCardViewModel</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> '~/types/viewModels'</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;">// Komponente kennt NUR das ViewModel, nicht GraphQL</span></span>
|
||
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;">defineProps</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"><{</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;"> user</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B;"> UserCardViewModel</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">}>()</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"></</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">script</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">></span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>4. Composables kapseln Mapping:</strong></p><div class="language-typescript line-numbers-mode" data-highlighter="shiki" data-ext="typescript" data-title="typescript" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;">// composables/useUser.ts</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">computed</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> 'vue'</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">useQuery</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> '@vue/apollo-composable'</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">GET_USER</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> '~/graphql/queries'</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">toUserCardViewModel</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> '~/mappers/userMapper'</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">import</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;"> type</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">UserCardViewModel</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> '~/types/viewModels'</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">export</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;"> function</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;"> useUser</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic;">userId</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B;"> string</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">) {</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;"> const</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> { </span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B;">result</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B;">loading</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B;">error</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> } </span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;">=</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;"> useQuery</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B;">GET_USER</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">id</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;"> userId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> })</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;"> const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B;"> user</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;"> =</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;"> computed</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"><</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B;">UserCardViewModel</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;"> |</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;"> null</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">>(() </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">=></span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> {</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;"> if</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> (</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;">!</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;">result</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B;">value</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">?.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">User</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">) </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">return</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66;"> null</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;"> return</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;"> toUserCardViewModel</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;">result</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B;">value</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">User</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">)</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> })</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;"> return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> { </span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;">user</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;">loading</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;">error</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> }</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">}</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="ordnerstruktur" tabindex="-1"><a class="header-anchor" href="#ordnerstruktur"><span>Ordnerstruktur</span></a></h3><div class="language- line-numbers-mode" data-highlighter="shiki" data-ext="" data-title="" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span>webapp/</span></span>
|
||
<span class="line"><span>├── graphql/</span></span>
|
||
<span class="line"><span>│ ├── queries/</span></span>
|
||
<span class="line"><span>│ ├── mutations/</span></span>
|
||
<span class="line"><span>│ └── types/ # Generated by graphql-codegen</span></span>
|
||
<span class="line"><span>├── types/</span></span>
|
||
<span class="line"><span>│ └── viewModels.ts # UI-spezifische Interfaces</span></span>
|
||
<span class="line"><span>├── mappers/</span></span>
|
||
<span class="line"><span>│ ├── userMapper.ts</span></span>
|
||
<span class="line"><span>│ ├── postMapper.ts</span></span>
|
||
<span class="line"><span>│ └── index.ts</span></span>
|
||
<span class="line"><span>├── lib/</span></span>
|
||
<span class="line"><span>│ └── composables/</span></span>
|
||
<span class="line"><span>│ ├── useAuth.ts</span></span>
|
||
<span class="line"><span>│ ├── useUser.ts # Gibt ViewModel zurück</span></span>
|
||
<span class="line"><span>│ └── usePost.ts</span></span>
|
||
<span class="line"><span>├── components/ # Presentational (nur ViewModels)</span></span>
|
||
<span class="line"><span>│ ├── UserCard.vue</span></span>
|
||
<span class="line"><span>│ └── PostTeaser.vue</span></span>
|
||
<span class="line"><span>└── pages/ # Nutzen Composables</span></span>
|
||
<span class="line"><span> └── users/[id].vue</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="vorteile-1" tabindex="-1"><a class="header-anchor" href="#vorteile-1"><span>Vorteile</span></a></h3><table><thead><tr><th>Aspekt</th><th>Ohne Mapper</th><th>Mit Mapper</th></tr></thead><tbody><tr><td><strong>API-Änderung</strong></td><td>Alle Komponenten anpassen</td><td>Nur Mapper anpassen</td></tr><tr><td><strong>Testing</strong></td><td>Mock GraphQL Response</td><td>Einfaches ViewModel-Objekt</td></tr><tr><td><strong>Wiederverwendung</strong></td><td>Komponente an API gebunden</td><td>Komponente API-agnostisch</td></tr><tr><td><strong>TypeScript</strong></td><td>Komplexe/generierte Types</td><td>Klare, einfache Interfaces</td></tr><tr><td><strong>webapp ↔ maintenance</strong></td><td>Verschiedene Strukturen</td><td>Gleiche ViewModels</td></tr></tbody></table><h3 id="regeln" tabindex="-1"><a class="header-anchor" href="#regeln"><span>Regeln</span></a></h3><div class="language- line-numbers-mode" data-highlighter="shiki" data-ext="" data-title="" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span>┌─────────────────────────────────────────────────────────────┐</span></span>
|
||
<span class="line"><span>│ Regel 1: Komponenten definieren was sie BRAUCHEN │</span></span>
|
||
<span class="line"><span>│ (ViewModel), nicht was die API LIEFERT │</span></span>
|
||
<span class="line"><span>├─────────────────────────────────────────────────────────────┤</span></span>
|
||
<span class="line"><span>│ Regel 2: Mapper sind der EINZIGE Ort der API kennt │</span></span>
|
||
<span class="line"><span>│ API-Änderung = nur Mapper ändern │</span></span>
|
||
<span class="line"><span>├─────────────────────────────────────────────────────────────┤</span></span>
|
||
<span class="line"><span>│ Regel 3: Composables kapseln Fetching + Mapping │</span></span>
|
||
<span class="line"><span>│ useUser() gibt UserCardViewModel zurück │</span></span>
|
||
<span class="line"><span>├─────────────────────────────────────────────────────────────┤</span></span>
|
||
<span class="line"><span>│ Regel 4: Presentational Components sind API-agnostisch │</span></span>
|
||
<span class="line"><span>│ Einfach testbar, wiederverwendbar │</span></span>
|
||
<span class="line"><span>└─────────────────────────────────────────────────────────────┘</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="entscheidung-1" tabindex="-1"><a class="header-anchor" href="#entscheidung-1"><span>Entscheidung</span></a></h3><table><thead><tr><th>#</th><th>Datum</th><th>Entscheidung</th></tr></thead><tbody><tr><td>73</td><td>2026-02-09</td><td>ViewModel/Mapper Pattern für Daten-Entkopplung</td></tr></tbody></table><hr><h2 id="_17-externe-abhangigkeiten" tabindex="-1"><a class="header-anchor" href="#_17-externe-abhangigkeiten"><span>17. Externe Abhängigkeiten</span></a></h2><h3 id="ubersicht-1" tabindex="-1"><a class="header-anchor" href="#ubersicht-1"><span>Übersicht</span></a></h3><table><thead><tr><th>Abhängigkeit</th><th>Status</th><th>Beschreibung</th></tr></thead><tbody><tr><td><strong>eslint-config-it4c</strong></td><td>⚠️ ESLint 10 ausstehend</td><td>v0.8.0 eingebunden, ESLint 10 inkompatibel</td></tr></tbody></table><h3 id="eslint-config-it4c" tabindex="-1"><a class="header-anchor" href="#eslint-config-it4c"><span>eslint-config-it4c</span></a></h3><p><strong>Status:</strong> Funktional, ESLint 10 Update ausstehend</p><p><strong>TODO:</strong> eslint-config-it4c muss für ESLint 10 aktualisiert werden (aktuell inkompatibel wegen @typescript-eslint/utils).</p><p><strong>Lösung:</strong> Das Paket wurde in Version 0.8.0 modularisiert und unterstützt jetzt ESLint Flat Config.</p><p><strong>Aktuelle Nutzung in @ocelot-social/ui:</strong></p><div class="language-typescript line-numbers-mode" data-highlighter="shiki" data-ext="typescript" data-title="typescript" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;">// eslint.config.ts</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">import</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> config</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">vue3</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">vitest</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> 'eslint-config-it4c'</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">export</span><span style="--shiki-light:#E45649;--shiki-dark:#C678DD;"> default</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> [</span></span>
|
||
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;"> ...</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">config</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;">// Base + TypeScript + Prettier + weitere</span></span>
|
||
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;"> ...</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">vue3</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;">// Vue 3 Regeln</span></span>
|
||
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;"> ...</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">vitest</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;">// Vitest Test-Regeln</span></span>
|
||
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;"> // Projekt-spezifische Overrides...</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">]</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>Projekt-spezifische Anpassungen:</strong></p><ul><li><code>n/file-extension-in-import</code>: Ausnahmen für <code>.vue</code>, <code>.css</code>, <code>.scss</code>, <code>.json</code></li><li><code>import-x/no-unassigned-import</code>: CSS-Imports erlaubt</li><li><code>vitest/consistent-test-filename</code>: Pattern <code>*.spec.ts</code></li><li><code>vitest/prefer-expect-assertions</code>: Ausgeschaltet</li><li><code>vitest/no-hooks</code>: Ausgeschaltet</li><li>Example Apps: Eigene ESLint-Configs (ignoriert in Hauptpaket, gelintet im Compatibility-Workflow)</li></ul><hr><h2 id="_18-kompatibilitatstests-details" tabindex="-1"><a class="header-anchor" href="#_18-kompatibilitatstests-details"><span>18. Kompatibilitätstests (Details)</span></a></h2><h3 id="testmatrix" tabindex="-1"><a class="header-anchor" href="#testmatrix"><span>Testmatrix</span></a></h3><p>Die Library muss in 4 Kombinationen funktionieren:</p><div class="language- line-numbers-mode" data-highlighter="shiki" data-ext="" data-title="" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span> │ Tailwind │ CSS (vorkompiliert)</span></span>
|
||
<span class="line"><span>────────────────────┼───────────────────┼──────────────────────</span></span>
|
||
<span class="line"><span>Vue 3.4+ │ ✓ Muss testen │ ✓ Muss testen</span></span>
|
||
<span class="line"><span>Vue 2.7 │ ✓ Muss testen │ ✓ Muss testen</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="werkzeuge-strategien" tabindex="-1"><a class="header-anchor" href="#werkzeuge-strategien"><span>Werkzeuge & Strategien</span></a></h3><h4 id="_1-vue-demi-code-ebene" tabindex="-1"><a class="header-anchor" href="#_1-vue-demi-code-ebene"><span>1. vue-demi (Code-Ebene)</span></a></h4><p>Abstrahiert Vue 2/3 API-Unterschiede:</p><div class="language-typescript line-numbers-mode" data-highlighter="shiki" data-ext="typescript" data-title="typescript" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;">// Komponente importiert von vue-demi, nicht vue</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">ref</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">computed</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">defineComponent</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> 'vue-demi'</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div></div></div><h4 id="_2-unit-tests-mit-vitest" tabindex="-1"><a class="header-anchor" href="#_2-unit-tests-mit-vitest"><span>2. Unit Tests mit Vitest</span></a></h4><p>Hauptpaket testet nur mit Vue 3 (Entwicklungsumgebung). Vue 2 Kompatibilität wird via vue-demi gewährleistet und in Example Apps getestet.</p><p><strong>Begründung:</strong> Inline Vue 2/3 Matrix verursacht Peer-Dependency-Konflikte.</p><h4 id="_3-example-apps-4-kombinationen-hauptstrategie-fur-vue-2-tests" tabindex="-1"><a class="header-anchor" href="#_3-example-apps-4-kombinationen-hauptstrategie-fur-vue-2-tests"><span>3. Example Apps (4 Kombinationen) - Hauptstrategie für Vue 2 Tests</span></a></h4><div class="language- line-numbers-mode" data-highlighter="shiki" data-ext="" data-title="" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span>packages/ui/</span></span>
|
||
<span class="line"><span>├── examples/</span></span>
|
||
<span class="line"><span>│ ├── vue3-tailwind/ # Vite + Vue 3 + Tailwind Preset</span></span>
|
||
<span class="line"><span>│ │ ├── package.json</span></span>
|
||
<span class="line"><span>│ │ ├── vite.config.ts</span></span>
|
||
<span class="line"><span>│ │ ├── tailwind.config.js # Nutzt @ocelot-social/ui/tailwind.preset</span></span>
|
||
<span class="line"><span>│ │ └── src/App.vue # Importiert alle Komponenten</span></span>
|
||
<span class="line"><span>│ │</span></span>
|
||
<span class="line"><span>│ ├── vue3-css/ # Vite + Vue 3 + style.css</span></span>
|
||
<span class="line"><span>│ │ ├── package.json</span></span>
|
||
<span class="line"><span>│ │ ├── vite.config.ts</span></span>
|
||
<span class="line"><span>│ │ └── src/</span></span>
|
||
<span class="line"><span>│ │ ├── main.ts # import '@ocelot-social/ui/style.css'</span></span>
|
||
<span class="line"><span>│ │ └── App.vue</span></span>
|
||
<span class="line"><span>│ │</span></span>
|
||
<span class="line"><span>│ ├── vue2-tailwind/ # Vue CLI / Nuxt 2 + Tailwind</span></span>
|
||
<span class="line"><span>│ │ └── ...</span></span>
|
||
<span class="line"><span>│ │</span></span>
|
||
<span class="line"><span>│ └── vue2-css/ # Vue CLI / Nuxt 2 + style.css</span></span>
|
||
<span class="line"><span>│ └── ...</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>Jede Example App:</strong></p><ul><li>Importiert <code>@ocelot-social/ui</code> via Workspace-Link</li><li>Rendert alle exportierten Komponenten</li><li>Kann lokal gestartet werden (<code>npm run dev</code>)</li><li>Wird in CI gebaut und getestet</li></ul><h4 id="_4-playwright-e2e-tests" tabindex="-1"><a class="header-anchor" href="#_4-playwright-e2e-tests"><span>4. Playwright E2E Tests</span></a></h4><div class="language-typescript line-numbers-mode" data-highlighter="shiki" data-ext="typescript" data-title="typescript" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;">// e2e/compatibility.spec.ts</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">test</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">expect</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> '@playwright/test'</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B;"> examples</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> [</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">name</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> 'vue3-tailwind'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">port</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66;"> 3001</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> },</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">name</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> 'vue3-css'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">port</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66;"> 3002</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> },</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">name</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> 'vue2-tailwind'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">port</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66;"> 3003</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> },</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">name</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> 'vue2-css'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">port</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF;">:</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66;"> 3004</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> },</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">]</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">for</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> (</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B;"> example</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;"> of</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;"> examples</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">) {</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;"> test</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;">describe</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;">example</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, () </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">=></span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> {</span></span>
|
||
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;"> test</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">'all components render'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">async</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> ({ </span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic;">page</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> }) </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">=></span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> {</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;"> await</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;"> page</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;">goto</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">`http://localhost:</span><span style="--shiki-light:#CA1243;--shiki-dark:#C678DD;">${</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;">example</span><span style="--shiki-light:#50A14F;--shiki-dark:#ABB2BF;">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">port</span><span style="--shiki-light:#CA1243;--shiki-dark:#C678DD;">}</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">`</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">)</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;"> // Prüfe dass alle Komponenten sichtbar sind</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;"> await</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;"> expect</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;">page</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;">locator</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">'[data-testid="os-button"]'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">)).</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;">toBeVisible</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">()</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;"> await</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;"> expect</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;">page</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;">locator</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">'[data-testid="os-card"]'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">)).</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;">toBeVisible</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">()</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;"> await</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;"> expect</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;">page</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;">locator</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">'[data-testid="os-modal"]'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">)).</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;">toBeVisible</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">()</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> })</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;"> test</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">'styles are applied correctly'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">async</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> ({ </span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic;">page</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> }) </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">=></span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> {</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;"> await</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;"> page</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;">goto</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">`http://localhost:</span><span style="--shiki-light:#CA1243;--shiki-dark:#C678DD;">${</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;">example</span><span style="--shiki-light:#50A14F;--shiki-dark:#ABB2BF;">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">port</span><span style="--shiki-light:#CA1243;--shiki-dark:#C678DD;">}</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">`</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">)</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;"> const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B;"> button</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2;"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;"> page</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;">locator</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">'[data-testid="os-button-primary"]'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">)</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;"> // Prüfe dass CSS korrekt angewendet wird</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;"> await</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;"> expect</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;">button</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">).</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;">toHaveCSS</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">'background-color'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">,</span><span style="--shiki-light:#0184BC;--shiki-dark:#E06C75;"> /rgb/</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">)</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> })</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;"> test</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">'visual regression'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">async</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> ({ </span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic;">page</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> }) </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">=></span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> {</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;"> await</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;"> page</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;">goto</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">`http://localhost:</span><span style="--shiki-light:#CA1243;--shiki-dark:#C678DD;">${</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;">example</span><span style="--shiki-light:#50A14F;--shiki-dark:#ABB2BF;">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">port</span><span style="--shiki-light:#CA1243;--shiki-dark:#C678DD;">}</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">`</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">)</span></span>
|
||
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;"> await</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;"> expect</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75;">page</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">).</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF;">toHaveScreenshot</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">`</span><span style="--shiki-light:#CA1243;--shiki-dark:#C678DD;">${</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B;">example</span><span style="--shiki-light:#50A14F;--shiki-dark:#ABB2BF;">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">name</span><span style="--shiki-light:#CA1243;--shiki-dark:#C678DD;">}</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">.png`</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">)</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> })</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> })</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">}</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4 id="_5-package-validation" tabindex="-1"><a class="header-anchor" href="#_5-package-validation"><span>5. Package Validation</span></a></h4><table><thead><tr><th>Tool</th><th>Zweck</th></tr></thead><tbody><tr><td><strong>publint</strong></td><td>Prüft package.json auf Export-Fehler</td></tr><tr><td><strong>arethetypeswrong</strong></td><td>Prüft TypeScript-Typen für alle Entry Points</td></tr></tbody></table><div class="language-json line-numbers-mode" data-highlighter="shiki" data-ext="json" data-title="json" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">{</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> "scripts"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: {</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> "check:exports"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">"publint && attw --pack ."</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">,</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> "prepublishOnly"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">"npm run check:exports"</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> }</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">}</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4 id="_6-package-json-exports-korrekte-struktur" tabindex="-1"><a class="header-anchor" href="#_6-package-json-exports-korrekte-struktur"><span>6. package.json Exports (korrekte Struktur)</span></a></h4><div class="language-json line-numbers-mode" data-highlighter="shiki" data-ext="json" data-title="json" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">{</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> "name"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">"@ocelot-social/ui"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">,</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> "type"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">"module"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">,</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> "exports"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: {</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> "."</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: {</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> "import"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">"./dist/index.mjs"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">,</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> "require"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">"./dist/index.cjs"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">,</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> "types"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">"./dist/index.d.ts"</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> },</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> "./style.css"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">"./dist/style.css"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">,</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> "./tailwind.preset"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: {</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> "import"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">"./dist/tailwind.preset.mjs"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">,</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> "require"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">"./dist/tailwind.preset.cjs"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">,</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> "types"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">"./dist/tailwind.preset.d.ts"</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> }</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> },</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> "peerDependencies"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: {</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> "vue"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">"^2.7.0 || ^3.0.0"</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> },</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> "peerDependenciesMeta"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: {</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> "tailwindcss"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: {</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> "optional"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#0184BC;--shiki-dark:#D19A66;">true</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> }</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> }</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">}</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="ci-workflow-compatibility-matrix" tabindex="-1"><a class="header-anchor" href="#ci-workflow-compatibility-matrix"><span>CI Workflow: Compatibility Matrix</span></a></h3><div class="language-yaml line-numbers-mode" data-highlighter="shiki" data-ext="yaml" data-title="yaml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic;"># .github/workflows/compatibility.yml</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">Compatibility Tests</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66;">on</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: [</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">push</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">pull_request</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">]</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">jobs</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">:</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> build</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">:</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> runs-on</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">ubuntu-latest</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> steps</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">:</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">uses</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">actions/checkout@v4</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">uses</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">actions/setup-node@v4</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> with</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">:</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> node-version</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">'20'</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">run</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">npm ci</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">run</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">npm run build</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">uses</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">actions/upload-artifact@v4</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> with</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">:</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">dist</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> path</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">packages/ui/dist</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> test-unit</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">:</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> needs</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">build</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> runs-on</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">ubuntu-latest</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> strategy</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">:</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> fail-fast</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66;">false</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> matrix</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">:</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> vue-version</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: [</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">'2.7'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">'3.4'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">]</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> steps</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">:</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">uses</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">actions/checkout@v4</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">uses</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">actions/download-artifact@v4</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> with</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">:</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">dist</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> path</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">packages/ui/dist</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">run</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">npm ci</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">run</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">npm test</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> env</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">:</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> VUE_VERSION</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">${{ matrix.vue-version }}</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> test-e2e</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">:</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> needs</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">build</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> runs-on</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">ubuntu-latest</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> strategy</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">:</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> fail-fast</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66;">false</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> matrix</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">:</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> example</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: [</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">'vue3-tailwind'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">'vue3-css'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">'vue2-tailwind'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">, </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">'vue2-css'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">]</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> steps</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">:</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">uses</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">actions/checkout@v4</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">uses</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">actions/download-artifact@v4</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> with</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">:</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">dist</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> path</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">packages/ui/dist</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">Setup example app</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> working-directory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">packages/ui/examples/${{ matrix.example }}</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> run</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">|</span></span>
|
||
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> npm ci</span></span>
|
||
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> npm run build</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">Start preview server</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> working-directory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">packages/ui/examples/${{ matrix.example }}</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> run</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">npm run preview &</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">Wait for server</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> run</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">npx wait-on http://localhost:4173 --timeout 30000</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">Run Playwright tests</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> working-directory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">packages/ui</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> run</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">npx playwright test --grep "${{ matrix.example }}"</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">uses</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">actions/upload-artifact@v4</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> if</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">failure()</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> with</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">:</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">playwright-report-${{ matrix.example }}</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> path</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">packages/ui/playwright-report/</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> validate-package</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">:</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> needs</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">build</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> runs-on</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">ubuntu-latest</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> steps</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">:</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">uses</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">actions/checkout@v4</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">uses</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">actions/download-artifact@v4</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> with</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">:</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">dist</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> path</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">packages/ui/dist</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">run</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">npm ci</span></span>
|
||
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;"> - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;">name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;">Validate exports</span></span>
|
||
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75;"> run</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF;">: </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD;">|</span></span>
|
||
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> npx publint packages/ui</span></span>
|
||
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379;"> npx @arethetypeswrong/cli packages/ui</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="werkzeug-ubersicht" tabindex="-1"><a class="header-anchor" href="#werkzeug-ubersicht"><span>Werkzeug-Übersicht</span></a></h3><table><thead><tr><th>Werkzeug</th><th>Zweck</th><th>Phase</th></tr></thead><tbody><tr><td><strong>vue-demi</strong></td><td>Vue 2/3 API-Kompatibilität im Code</td><td>Phase 2</td></tr><tr><td><strong>Vitest + Matrix</strong></td><td>Unit Tests für Vue 2.7 und Vue 3.4</td><td>Phase 2</td></tr><tr><td><strong>Example Apps (4x)</strong></td><td>Echte Projekte für jede Kombination</td><td>Phase 2</td></tr><tr><td><strong>Playwright</strong></td><td>E2E + Visual Regression für alle 4</td><td>Phase 2</td></tr><tr><td><strong>publint</strong></td><td>Package.json Export-Validierung</td><td>Phase 2</td></tr><tr><td><strong>arethetypeswrong</strong></td><td>TypeScript Entry Points Check</td><td>Phase 2</td></tr><tr><td><strong>pkg-pr-new</strong></td><td>Preview-Releases in PRs (optional)</td><td>Optional</td></tr></tbody></table><h3 id="checkliste-fur-neue-komponenten" tabindex="-1"><a class="header-anchor" href="#checkliste-fur-neue-komponenten"><span>Checkliste für neue Komponenten</span></a></h3><div class="language- line-numbers-mode" data-highlighter="shiki" data-ext="" data-title="" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span>[ ] Unit Tests laufen mit VUE_VERSION=2.7</span></span>
|
||
<span class="line"><span>[ ] Unit Tests laufen mit VUE_VERSION=3.4</span></span>
|
||
<span class="line"><span>[ ] Komponente in allen 4 Example Apps hinzugefügt</span></span>
|
||
<span class="line"><span>[ ] E2E Tests für Komponente geschrieben</span></span>
|
||
<span class="line"><span>[ ] Visual Regression Baseline erstellt</span></span>
|
||
<span class="line"><span>[ ] Keine Vue 3-only APIs verwendet (oder via vue-demi abstrahiert)</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><hr><h2 id="_19-komplexitatsanalyse" tabindex="-1"><a class="header-anchor" href="#_19-komplexitatsanalyse"><span>19. Komplexitätsanalyse</span></a></h2><h3 id="umfang-nach-phasen" tabindex="-1"><a class="header-anchor" href="#umfang-nach-phasen"><span>Umfang nach Phasen</span></a></h3><table><thead><tr><th>Phase</th><th>Aufgaben</th><th>Komplexität</th><th>Abhängigkeiten</th></tr></thead><tbody><tr><td>Phase 0: Analyse</td><td>6 Tasks</td><td>✅ Erledigt</td><td>-</td></tr><tr><td>Phase 1: Vue 2.7</td><td>6 Tasks</td><td>✅ Erledigt</td><td>-</td></tr><tr><td>Phase 2: Setup</td><td>26 Tasks</td><td>Mittel</td><td>⚠️ eslint-config-it4c (extern)</td></tr><tr><td>Phase 3: Tokens</td><td>6 Tasks</td><td>Niedrig</td><td>Keine externen</td></tr><tr><td>Phase 4: Migration</td><td>15 Komponenten</td><td>Hoch</td><td>Pro Komponente: Spec→Dev→Test→Integrate</td></tr><tr><td>Phase 5: Finalisierung</td><td>7 Tasks</td><td>Niedrig</td><td>Alle vorherigen Phasen</td></tr></tbody></table><h3 id="bekannte-risikofaktoren" tabindex="-1"><a class="header-anchor" href="#bekannte-risikofaktoren"><span>Bekannte Risikofaktoren</span></a></h3><table><thead><tr><th>Risiko</th><th>Beschreibung</th><th>Auswirkung</th><th>Mitigation</th></tr></thead><tbody><tr><td><strong>eslint-config-it4c</strong></td><td>Externes Projekt muss zuerst modularisiert werden</td><td>Blockiert Linting-Setup in Phase 2</td><td>Temporäre lokale ESLint-Config als Workaround</td></tr><tr><td><strong>vue-demi Kompatibilität</strong></td><td>Unbekannte Edge-Cases bei Vue 2/3 Dual-Support</td><td>Unerwartete Bugs bei Integration</td><td>Frühzeitig in Example Apps testen</td></tr><tr><td><strong>Visual Regression Baselines</strong></td><td>Können bei Design-Änderungen viel Nacharbeit erfordern</td><td>Zusätzlicher Aufwand bei Änderungen</td><td>Baselines erst nach Design-Freeze erstellen</td></tr><tr><td><strong>Feature-Parity</strong></td><td>Alte Komponenten haben undokumentierte Verhaltensweisen</td><td>Regressions bei Migration</td><td>Gründliche Analyse vor Implementierung</td></tr><tr><td><strong>Tailwind + CSS Dual-Build</strong></td><td>Komplexe Build-Konfiguration</td><td>Build-Fehler, Inkonsistenzen</td><td>Früh beide Varianten parallel testen</td></tr></tbody></table><h3 id="parallelisierbarkeit" tabindex="-1"><a class="header-anchor" href="#parallelisierbarkeit"><span>Parallelisierbarkeit</span></a></h3><table><thead><tr><th>Phase</th><th>Parallelisierbar</th><th>Details</th></tr></thead><tbody><tr><td>Phase 2</td><td>Teilweise</td><td>Die meisten Tasks sind sequentiell (Setup-Reihenfolge wichtig)</td></tr><tr><td>Phase 3</td><td>Nein</td><td>Token-Ebenen bauen aufeinander auf (Base → Semantic → Component)</td></tr><tr><td>Phase 4</td><td>Ja (nach Tier 1)</td><td>Tier 2/3 Komponenten können parallel entwickelt werden</td></tr><tr><td>Phase 5</td><td>Teilweise</td><td>Dokumentation kann parallel zur letzten Integration</td></tr></tbody></table><p><strong>Parallelisierbare Aufgaben in Phase 2:</strong></p><ul><li>4 Example Apps (vue3-tailwind, vue3-css, vue2-tailwind, vue2-css)</li><li>GitHub Workflows (unabhängig voneinander)</li><li>LICENSE, README.md, CONTRIBUTING.md</li></ul><p><strong>Sequentielle Abhängigkeiten in Phase 2:</strong></p><ol><li>Vite + Vue 3 Projekt → vue-demi → Tailwind → Build-Pipeline</li><li>Vitest → Tests → Visual Regression</li><li>Package-Struktur → release-please → npm Publish Workflow</li></ol><h3 id="aufwandstreiber-pro-komponente-phase-4" tabindex="-1"><a class="header-anchor" href="#aufwandstreiber-pro-komponente-phase-4"><span>Aufwandstreiber pro Komponente (Phase 4)</span></a></h3><p>Jede Komponente durchläuft:</p><div class="language- line-numbers-mode" data-highlighter="shiki" data-ext="" data-title="" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34;"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code><span class="line"><span>┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐</span></span>
|
||
<span class="line"><span>│ ANALYSE │ → │ SPEC │ → │ DEVELOP │ → │ QA │ → │ INTEGRATE │</span></span>
|
||
<span class="line"><span>├─────────────┤ ├─────────────┤ ├─────────────┤ ├─────────────┤ ├─────────────┤</span></span>
|
||
<span class="line"><span>│ Bestehende │ │ Props │ │ Vue 3 Code │ │ Unit Tests │ │ In Webapp │</span></span>
|
||
<span class="line"><span>│ Varianten │ │ Events │ │ TypeScript │ │ Vue 2 Tests │ │ einbinden │</span></span>
|
||
<span class="line"><span>│ analysieren │ │ Slots │ │ Tailwind │ │ A11y Tests │ │ │</span></span>
|
||
<span class="line"><span>│ │ │ Tokens │ │ Storybook │ │ Visual Reg. │ │ Alte Komp. │</span></span>
|
||
<span class="line"><span>│ │ │ A11y │ │ Stories │ │ 4 Examples │ │ entfernen │</span></span>
|
||
<span class="line"><span>└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘</span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>Aufwand variiert stark nach Komponente:</strong></p><table><thead><tr><th>Komponente</th><th>Komplexität</th><th>Grund</th></tr></thead><tbody><tr><td>OsIcon</td><td>Niedrig</td><td>Einfache Wrapper-Komponente</td></tr><tr><td>OsSpinner</td><td>Niedrig</td><td>Nur Animation + Größen</td></tr><tr><td>OsButton</td><td>Hoch</td><td>Viele Varianten, Link-Support, States</td></tr><tr><td>OsCard</td><td>Niedrig</td><td>Einfaches Layout</td></tr><tr><td>OsModal</td><td>Hoch</td><td>Teleport, Focus-Trap, Animations, A11y</td></tr><tr><td>OsDropdown</td><td>Hoch</td><td>Positioning, Click-Outside, Hover-States</td></tr><tr><td>OsInput</td><td>Mittel</td><td>Validierung, States, Icons</td></tr><tr><td>OsAvatar</td><td>Niedrig</td><td>Bild + Fallback</td></tr></tbody></table></div><!----><footer class="vp-page-meta"><div class="vp-meta-item edit-link"><a class="auto-link external-link vp-meta-label" href="https://github.com/Ocelot-Social-Community/Ocelot-Social/edit/master/./packages/ui/PROJEKT.md" aria-label="Edit this page" rel="noopener noreferrer" target="_blank"><!--[--><svg xmlns="http://www.w3.org/2000/svg" class="icon edit-icon" viewBox="0 0 1024 1024" fill="currentColor" aria-label="edit icon" name="edit"><path d="M430.818 653.65a60.46 60.46 0 0 1-50.96-93.281l71.69-114.012 7.773-10.365L816.038 80.138A60.46 60.46 0 0 1 859.225 62a60.46 60.46 0 0 1 43.186 18.138l43.186 43.186a60.46 60.46 0 0 1 0 86.373L588.879 565.55l-8.637 8.637-117.466 68.234a60.46 60.46 0 0 1-31.958 11.229z"></path><path d="M728.802 962H252.891A190.883 190.883 0 0 1 62.008 771.98V296.934a190.883 190.883 0 0 1 190.883-192.61h267.754a60.46 60.46 0 0 1 0 120.92H252.891a69.962 69.962 0 0 0-69.098 69.099V771.98a69.962 69.962 0 0 0 69.098 69.098h475.911A69.962 69.962 0 0 0 797.9 771.98V503.363a60.46 60.46 0 1 1 120.922 0V771.98A190.883 190.883 0 0 1 728.802 962z"></path></svg><!--]-->Edit this page<!----></a></div><div class="vp-meta-item git-info"><!----><!----></div></footer><!----><!----><!----><!--]--></main><!--]--><!--]--><footer class="vp-footer-wrapper" vp-footer><div class="vp-footer">Released under the <a class="vt-link link link" href="https://opensource.org/licenses/MIT" target="_blank" rel="noopener noreferrer"><!--[-->MIT License<!--]--><!----></a>.</div><div class="vp-copyright">© by <a href="https://busfaktor.org" target="_blank">busFaktor() e.V.</a> & Authors | <a href="https://ocelot.social/en/impressum/">Imprint</a></div></footer></div><!--]--><!--]--><!--[--><!----><!----><!--[--><!--]--><!--]--><!--]--></div>
|
||
<script type="module" src="/assets/app-C6w_Pklu.js" defer></script>
|
||
</body>
|
||
</html>
|