Konventionen
Dieses Projekt verwendet Tailwind CSS 4 mit CSS-basierter Konfiguration.
Custom Tokens in main.css:
@theme { --color-primary-*: ...; --color-accent-*: ...;}Dark Mode
Abschnitt betitelt „Dark Mode“Klassenbasiert über Custom Variant:
@custom-variant dark (&:where(.dark, .dark *));Kein @apply
Abschnitt betitelt „Kein @apply“Alle Custom-Klassen verwenden native CSS mit CSS Custom Properties:
.my-class { color: var(--color-primary-500); padding: var(--spacing); border-radius: var(--radius-lg);}Dark-Mode in Scoped Styles
Abschnitt betitelt „Dark-Mode in Scoped Styles“:is(.dark .my-class) { color: var(--color-primary-300);}Wichtig
Abschnitt betitelt „Wichtig“- Kein
@applyverwenden — das ist ein Tailwind 3 Pattern - Alle Views müssen genau ein Root-Element haben (wegen
<Transition mode="out-in">inApp.vue)
Offline-Fähige Store-Methoden
Abschnitt betitelt „Offline-Fähige Store-Methoden“Methoden, die offline funktionieren sollen, folgen einem einheitlichen Pattern:
apiRawstattapi.put/post/delverwenden — verhindert automatische Fehler-Toasts- Optimistisches UI-Update vor dem API-Call — lokalen State sofort ändern
- catch-Block mit
offlineQueue.isOfflineError(err)— bei Netzwerkfehler in die Queue einreihen - Rollback bei nicht-offline Fehlern — lokalen State zurücksetzen
// Pattern für offline-fähige Store-Methodeasync toggleItem(itemId, newState) { // 1. Optimistisch updaten const previous = item.is_checked; item.is_checked = newState;
try { // 2. apiRaw statt api.put (kein Auto-Toast) await apiRaw(`/shopping/item/${itemId}/check`, { method: 'PUT', body: { is_checked: newState }, }); } catch (err) { if (offlineQueue.isOfflineError(err)) { // 3. In Queue einreihen await offlineQueue.enqueue({ type: 'shopping:toggleItem', payload: { itemId, is_checked: newState }, storeName: 'shopping', }); } else { // 4. Rollback bei echtem Fehler item.is_checked = previous; } }}Idempotente Backend-Endpunkte
Abschnitt betitelt „Idempotente Backend-Endpunkte“Offline-fähige Endpunkte müssen idempotent sein — derselbe Call mit denselben Daten darf nicht zu inkonsistentem State führen. Statt Toggles (!current) einen expliziten Zielwert akzeptieren (is_checked: 1).
Haushalt-Awareness (resolveHousehold)
Abschnitt betitelt „Haushalt-Awareness (resolveHousehold)“Routen, die haushaltsbezogene Daten verarbeiten, verwenden den resolveHousehold-Decorator statt authenticate:
- Authentifiziert den Benutzer (JWT oder API-Key)
- Liest
X-Household-Idaus dem Request-Header - Validiert die Mitgliedschaft — bei ungültigem Haushalt:
403 Forbidden - Setzt
request.householdId(odernullwenn kein Haushalt gewählt)
Queries nutzen householdWhereClause(userId, householdId) — gibt eine { clause, params }-Struktur zurück, die sowohl private als auch geteilte Daten erfasst.
X-Household-Id Header
Abschnitt betitelt „X-Household-Id Header“Der Frontend-API-Client (useApi.js) sendet automatisch den X-Household-Id-Header aus localStorage, wenn ein aktiver Haushalt gesetzt ist. Dadurch sind keine View-Änderungen nötig — die Datenfilterung erfolgt transparent im Backend.
SSE-Verbindungen
Abschnitt betitelt „SSE-Verbindungen“Für Echtzeit-Updates im Haushalt wird EventSource verwendet. Da EventSource keine Custom-Header unterstützt, wird das JWT als Query-Parameter übergeben:
const url = `/api/household-events/${householdId}?token=${jwt}`;const es = new EventSource(url);