frontend – Bildschirme, die für jeden Benutzer, in jedem Zustand funktionieren und leicht änderbar bleiben
Beachten Sie zuerst die CLAUDE.md, docs/conventions.md und docs/design-system.md des Repositorys; dieses Skill
füllt Lücken und überschreibt sie nie. Visuelle Arbeit (Tokens, Layout, Farbe, CSS) gehört zu
css-design; das Ausführen der App wie der Benutzer und das endgültige Urteil gehören zu verify-app.
Wählen Sie den Stack. Vue 3 und React sind gleichberechtigt und folgen denselben Regeln. Verwenden Sie das
Framework, das das Repository bereits hat; für eine neue App verwenden Sie das Frontend-Standard aus dem Lieferprofil
(siehe README des Repositorys), sonst fragen Sie einmal im Triage. Vue: TypeScript + Vite (npm create vue@latest -- --ts --router --pinia --vitest --playwright --eslint --prettier) mit references/vue-patterns.md.
React: TypeScript + Vite (oder das Meta-Framework des Repositorys) mit references/react-patterns.md. Mischen Sie niemals
Frameworks in einer App. Benötigen Sie SSR oder SSG für SEO? Dokumentieren Sie Nuxt oder Next.js in einem ADR (architect);
dieses Skill deckt in beiden Fällen die Client-seitigen Teile ab.
Arbeitsablauf
- Laden Sie die Konventionen und die Bestandsaufnahme des Repositorys. Lesen Sie
CLAUDE.md,docs/conventions.md,docs/design-system.md; listen Sie wiederverwendbare Teile auf (ls src/components src/features src/composables src/hooks src/stores). Erkennen Sie die Daten-(Layer), Router-, Formular- und i18n-Bibliotheken (Vue:grep -E '"(@tanstack/vue-query|pinia|vue-router|vee-validate|vue-i18n)"' package.json; React:grep -E '"(@tanstack/react-query|@tanstack/react-router|react-router|react-hook-form|i18next|react-intl)"' package.json). Fügen Sie niemals eine zweite Daten-(Layer), Router-, Store-, Formular- oder i18n-Bibliothek hinzu. Nachweis: Die Notizen der Story listen die wiederverwendeten Komponenten und Composables/Hooks sowie alle neuen auf, mit Begründung. - Schreiben Sie die Zustandstabelle vor dem Code. Für jede Ansicht: jedes Datenelement und wo es lebt –
Server (Query), URL (Route-Parameter / Suche), gemeinsam genutzter Client (Pinia-Store, oder React-Kontext oder der
Store des Repositorys), lokal (
ref/useState), abgeleitet (computed/ eine Konstante im Render), extern (ein Composable oderuseSyncExternalStore, das die Browser-API umschließt). Listen Sie dann die vier Zustände auf (Laden, Leer, Fehler, Erfolg) und die Kopier-Schlüssel für jeden. Nachweis: Die Tabelle in der Story. - Schreiben Sie zuerst die fehlschlagenden Tests. Vitest +
@testing-library/vueoder@testing-library/react,userEvent.setup(), Abfragen nach Rolle und zugänglichem Namen, ein Verhalten pro Test, Netzwerk an der Service-Grenze simuliert (MSW oder der Fake-Client des Repositorys insrc/test-utils). Decken Sie jeden Zustand, den Tastaturpfad und einen negativen Fall ab. Nachweis: Der Lauf zeigt aus dem richtigen Grund Rot. - Bauen Sie innerhalb des Feature-Ordners.
src/features/<name>/services(typisierte API-Aufrufe, kein Framework) →composables/hooks(useX, dasuseQuery/useMutationund eine Key-Factory umschließt) →components(Name.vuemit<script setup lang="ts">, oderName.tsx) → die Routen-Ansicht, die sie zusammensetzt. Props und Events typisiert, keinany, kein Non-Null!,@/-Imports. Nachweis:git diff --statbleibt innerhalb des Features plus bewusste gemeinsame Änderungen. - Kopieren und Formatieren. Jede sichtbare Zeichenkette stammt aus den Lokalisierungsdateien (vue-i18n
useI18n(), oder der i18n-Bibliothek oder dem Copy-Modul des React-Repositorys) mit denselben Schlüsseln in jedem Gebietsschema; ein Test erzwingt Parität. Zahlen und Daten über den i18n-Formatierer oderIntl. Nachweis: Paritätstest grün; keine hartcodierten Kopien, erzwungen durch Lint (@intlify/vue-i18n/no-raw-text; React:react/jsx-no-literalsoder die Regel des i18n-Plugins) – zitieren Sie den Lint-Exit-Code. - Gestalten Sie es mit
css-design. Nur Tokens, BEM-Block = Komponentenname, jeder Zustand gestaltet;.vue-Styles durchpostcss-htmlgelintet. Nachweis: Stylelint grün. - Stories (Komponentenbibliotheken). Eine Geschichte pro Zustand, beide Themes; Storybook axe besteht. Nachweis: Die Stories-Tests und der axe-Lauf.
- Gates, verkettet durch Exit-Code.
npm run lint && npm run type-check && npm run test:unit -- --run(Vue:vue-tsc --build; React:tsc -b), odermake ci; nie von| tailaus beurteilen. Nachweis: Exit-Code 0 und Abdeckung auf oder über dem Schwellenwert des Repositorys. - Journey in einem echten Browser gegen den Production-Build.
npm run buildund servieren Sie die Ausgabe (vite preview, oder der statische Server, den das Repository verwendet). Fügen Sie eine Playwright-Spezifikation für den Hauptpfad mit@axe-core/playwrighthinzu oder erweitern Sie sie (Tagswcag2a wcag2aa wcag21aa wcag22aa, schwerwiegend und kritisch = 0). Erfassen Siedocs/delivery/<slug>/evidence/<view>-390.pngund-1280.png, plus Dark-Theme, falls die App eines hat. Öffnen Sie jeden Screenshot, bevor Sie ihn zitieren. Nachweis: Spezifikation grün, PNGs. - Tastatur- und Screenreader-Prüfung. Tabben Sie durch die neue Ansicht: Reihenfolge, sichtbarer Fokus, Escape
schließt Overlays und der Fokus kehrt zum Auslöser zurück; nach der Navigation landet der Fokus auf der neuen Überschrift
und der Titel hat sich geändert. Überprüfen Sie stichprobenartig Namen und Ansagen mit einem Screenreader (VoiceOver
Cmd+F5, oder NVDA). Nachweis: Ein dreizeiliger Hinweis im QA-Abschnitt der Story. - Übergabe. Führen Sie
verify-appfür den End-to-End-Anspruch aus; innerhalb vondeliver-loopbewertet die unabhängige Überprüfung das Element. Wiederholen Sie deren Schritte hier nicht.
Kern-Checkliste
- Nichts speichern, was abgeleitet werden kann:
computedverwenden (React: eine Konstante im Render); kein Watcher oder Effekt, dessen einzige Aufgabe es ist, anderen Zustand zu setzen. - Watcher und Effekte synchronisieren nur mit der Außenwelt (DOM-APIs, Abonnements, Timer, Drittanbieter-
Widgets) und räumen auf (
onWatcherCleanup/onUnmounted; React: die Rückgabe des Effekts). Arbeit, die durch eine Benutzeraktion verursacht wird, wird im Event-Handler ausgeführt. - Serverdaten werden niemals in den lokalen Zustand oder einen Store kopiert; Query-Keys enthalten jede Eingabe, die der Fetch verwendet (in Vue als Refs oder Getter, sodass eine Änderung erneut abruft).
- Auswahl, Filter, Tabs und Paginierung, die ein Benutzer teilen oder neu laden würde, leben in der URL-Query.
- Jede Datenansicht rendert Laden, Leer, Fehler (mit Wiederholung) und Erfolg; Tests decken alle vier ab.
- Jede Eingabe hat ein sichtbares
<label for>(IDs vonuseId()in beiden Frameworks); Fehler sind Text, der mitaria-describedbyverbunden ist, die Eingabe bleibt erhalten, der Fokus bewegt sich beim Absenden zum ersten ungültigen Feld. - Native Elemente zuerst (
button,a href/Router-Link,dialog,details,input type); keindiv @click/div onClick. - Benutzerdefinierte Widgets folgen dem entsprechenden WAI-ARIA APG-Muster, inklusive Tastatur.
- Asynchrone Ergebnisse, die den Fokus nicht verschieben, werden über einen
role="status"/role="alert"-Bereich angekündigt, der einmal gerendert und leer ist (Vue: teleportiert zubody; React: ein Portal, falls es die Ansicht überleben muss). - Routenwechsel:
document.titleaktualisiert und der Fokus auf die Seiteh1odermainverschoben (Vue: einrouter.afterEach+ Template-Ref; React: das Äquivalent des Routers). - Fehler werden pro Route und pro riskantem Widget abgefangen (Vue: eine
onErrorCaptured-Boundary-Komponente; React: eine Error-Boundary) und zeigen einen Wiederholungsversuch, niemals eine leere Seite. - Keine hartcodierten Anzeigezeichenfolgen; jedes Gebietsschema hat dieselben Schlüssel.
- Tests fragen nach Rolle/Label/Text ab, interagieren mit
user-eventund bestätigen niemals Klassennamen, Komponenten-Interna (wrapper.vm, Zustandswerte) oder Composable/Hook-Aufrufe. - Kein
v-html/dangerouslySetInnerHTML, es sei denn, es wird unmittelbar vor der Verwendung mit DOMPurify bereinigt; URLs aus Daten sind aufhttps:/mailto:/relativ zugelassen. shallowRef/markRaw/v-memo/KeepAlive(React:memo/useMemo/useCallback) nur mit einer Profiler-Messung oder einem dokumentierten Bedarf.- Diagramme und Datenvisualisierungen haben einen zugänglichen Namen und eine Zusammenfassung, eine Daten-Tabellen- (oder Text-)Alternative, eine farbenblind-sichere Palette, bei der Serien auch durch Beschriftung, Form oder Muster unterschieden werden, und Tastaturzugriff auf jede Interaktion – unabhängig von der verwendeten Diagrammbibliothek.
Anti-Patterns
Vue:
watch(items, v => { filtered.value = v.filter(...) })–computedverwenden.const { count } = reactive(state)oderconst { name } = store– Reaktivität verloren;toRefs/storeToRefsverwenden oderrefbevorzugen.watch(props.id, ...)oder ein destrukturiertes Prop an ein Composable übergeben – einen Getter() => idübergeben.- Ein Prop oder
useQuery-Daten mutieren; Query-Daten direkt anv-modelbinden – in ein Formular kopieren. v-ifundv-forauf demselben Element;:key="index"bei einer neu anordenbaren Liste.awaitvoronWatcherCleanupoder vor dem Registrieren von Lifecycle-Hooks in einem Composable.defineAsyncComponentfür eine Routenkomponente (Routen verwenden() => import(...)direkt).next()in einem Navigation-Guard –false, einen Ort oder nichts zurückgeben.- Tests, die in
wrapper.vmgreifen odersetDataaufrufen.
React:
useEffect(() => setFiltered(items.filter(...)), [items])– im Rendern berechnen.- Ein Formular in einem Effekt zurücksetzen, wenn sich eine ID ändert –
key={id}übergeben. - Hooks, die innerhalb von Bedingungen oder Schleifen aufgerufen werden; Zustand an Ort und Stelle mutiert, anstatt ersetzt zu werden.
key={index}bei einer neu anordenbaren Liste; ein neues Objekt oder eine neue Funktion als Prop bei jedem Rendern an ein memoisiertes Kind übergeben, das aus einem gemessenen Grund memoisiert wurde.
Beide:
- Fetching beim Mount ohne Abbruch, Cache, Wiederholung oder Fehlerzweig – die Daten-Layer verwenden.
- Spinner ohne leeren oder Fehlerzweig; Fehler in
console.logverschluckt. staleTimeauf 0 belassen für Daten, die sich täglich ändern, sodass jeder Fokus erneut abruft.- Optimistisches Update ohne Snapshot, Rollback und Invalidate bei Abschluss.
- Platzhalter als Label verwendet; Farbe allein markiert einen Fehler;
role="button"ohne Tasten. getByTestIdüberall; ganze Seiten-Snapshots; Testen von Implementierungsdetails.- Eine Feature-Barrel-Datei, die alles in den ersten Routen-Chunk zieht.
:href="user.url"/href={user.url}ohne Überprüfung des Schemas.
Nachweise vor „Erledigt“
- Fehlschlagende, dann bestehende Unit-Tests für jeden Zustand und den Tastaturpfad.
- Lint,
vue-tscodertsc, Tests verkettet, Exit 0; Abdeckung auf oder über dem Schwellenwert. - Playwright-Journey gegen den Production-Build, axe schwerwiegend/kritisch = 0.
- Screenshots bei 390 und 1280 px (und Dark-Theme) in
docs/delivery/<slug>/evidence/, jedes geöffnet. - Tastatur- und Screenreader-Hinweis im QA-Abschnitt der Story.
- Dann
verify-app.
Referenzen
references/vue-patterns.md– Die Vue-Checkliste: SFC und Component API, Reaktivitätsfallen, computed vs. watch, Composables, Pinia, TanStack Query für Vue, Vue Router (URL-Zustand, Guards, Fokus), Formulare, i18n, A11y-Spezifikationen, Leistung, Sicherheit, Tooling; jedes Element mit Quellenangabe.references/react-patterns.md– Die React-Checkliste: dieselben Themen, plus eine Variante für statischen Export (Next.js / Nuxt generieren).references/a11y-and-testing.md– WCAG 2.2 AA-Items für UI-Code, ARIA/APG, Tastatur und Live- Regionen, Diagramme, Testing Library für Vue und React, MSW, Playwright + axe, was nicht getestet werden soll.