# HaushaltsApp — Claude Instructions ## Projektübersicht Mobile-SaaS App für Haushaltsverwaltung: Haushaltsbuch, Urlaubsbudget, Echtzeit-Einkaufsliste. **Stack:** - **Monorepo:** Bun Workspaces + Turborepo - **Backend:** Hono (Bun) — `apps/server/` - **Mobile:** React Native + Expo + expo-router — `apps/native/` - **Web (Dashboard):** React + TanStack Router + Vite — `apps/web/` - **Database:** PostgreSQL + Drizzle ORM — `packages/db/` - **Auth:** Better Auth — `packages/auth/` - **Shared Types/Schemas:** Zod — `packages/shared/` - **UI (Web):** shadcn/ui (base-lyra) — `packages/ui/` - **UI (Native):** HeroUI Native + Uniwind ## Architektur-Entscheidungen - **Multi-Tenant via `householdId`:** Jeder Haushalt ist ein Tenant. Alle Daten (Transaktionen, Einkaufslisten etc.) sind an eine `householdId` gebunden. `householdId` wird via `x-household-id` HTTP-Header übergeben. - **Subscription Plans:** `free`, `pro`, `family` — Feature-Gates in `plan.middleware.ts`, Definitionen in `packages/shared/src/constants/plans.ts`. - **Better Auth** übernimmt User/Session-Management. Die `packages/auth/` enthält die Server-seitige Konfiguration. - **Shared Package (`@haushaltsApp/shared`):** Zod-Schemas und TypeScript-Types werden im shared package definiert und sowohl vom Backend als auch vom Frontend verwendet. - **Drizzle Schema:** Alle App-Tabellen in `packages/db/src/schema/app.ts`, Auth-Tabellen in `packages/db/src/schema/auth.ts`. ## Development starten ```bash # Alle Services starten bun run dev # Einzelne Services bun run dev:server # API auf http://localhost:3000 bun run dev:web # Web auf http://localhost:3001 bun run dev:native # Expo (Metro Bundler) # Datenbank bun run db:start # PostgreSQL via Docker starten bun run db:generate # Drizzle Migrationen generieren bun run db:migrate # Migrationen anwenden bun run db:studio # Drizzle Studio öffnen ``` ## Testing-Konventionen ### Backend (`apps/server/`) - **Framework:** `bun:test` (built-in, kein extra Package) - **Ort:** `apps/server/src/__tests__/routes/` und `apps/server/src/__tests__/services/` - **Konvention:** Route-Tests testen den HTTP-Layer direkt via `app.request()`. Services werden unit-getestet. - **Ausführen:** `bun run test:api` oder `bun test apps/server/src/__tests__` ```typescript // Beispiel Route-Test import { describe, expect, it } from "bun:test"; import app from "../../index"; describe("GET /health", () => { it("returns 200 with status ok", async () => { const res = await app.request("/health"); expect(res.status).toBe(200); }); }); ``` ### Mobile (`apps/native/`) - **Framework:** `bun:test` — kein Jest/Babel/jest-expo (Jest + Bun's .bun/ Store sind inkompatibel mit RN's ESM setup files) - **Was wird getestet:** Stores, Hooks, Utils, API-Client — reine Business Logic, kein React Native Rendering - **Was NICHT getestet wird:** UI-Komponenten, Screens — Rendering wird manuell via Expo Go verifiziert - **Ort:** `apps/native/src/__tests__/` spiegelt `apps/native/src/` Struktur - **Ausführen:** `bun run test:mobile` oder `bun test apps/native/src/__tests__` ```typescript // Beispiel Store-Test import { describe, expect, it, beforeEach } from "bun:test"; import { useAuthStore } from "../../stores/auth.store"; describe("authStore", () => { beforeEach(() => { useAuthStore.setState({ user: null, token: null, isAuthenticated: false }); }); it("setUser authenticates the user", () => { useAuthStore.getState().setUser({ id: "1", name: "Test", email: "t@t.com" }); expect(useAuthStore.getState().isAuthenticated).toBe(true); }); }); ``` ## Coding-Konventionen ### Allgemein - **TypeScript strict mode** überall — kein `any`, kein type-casting ohne Kommentar - **Zod-first:** Alle API-Inputs werden mit Zod-Schemas aus `@haushaltsApp/shared` validiert - **Named exports** bevorzugen (default exports nur bei Expo/React Router Screens) ### Naming - **Files:** `kebab-case.ts` / `PascalCase.tsx` für React-Komponenten - **Variables/Functions:** `camelCase` - **Types/Interfaces:** `PascalCase` - **Database tables:** `snake_case` (Drizzle convention) - **Zod schemas:** `camelCaseSchema` (z.B. `createTransactionSchema`) - **Route files:** `feature.routes.ts` - **Middleware files:** `feature.middleware.ts` - **Service files:** `feature.service.ts` ### API-Design - Alle Endpoints unter `/api/` prefix - Auth-Check via `authMiddleware` + `requireAuth` - Tenant-Check via `tenantMiddleware` + `requireHousehold` - Plan-Gates via `requireFeature('featureName')` - HTTP-Status-Codes: 200 OK, 201 Created, 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found ### Ordnerstruktur (Backend) ``` apps/server/src/ ├── routes/ # HTTP-Layer, minimale Logik ├── services/ # Business Logic, kein direkter DB-Code in routes ├── middleware/ # Auth, Tenant, Plan Feature Gates ├── websocket/ # WebSocket Handler ├── lib/ # DB-Instance, Utilities └── __tests__/ # Tests spiegeln src/-Struktur ``` ### State Management (Mobile) - **Zustand** für globalen Client-State (User, aktueller Haushalt) - **TanStack Query** für Server-State (Daten aus der API) - Kein direktes Fetch in Komponenten — immer Custom Hooks oder TanStack Query ## Known Constraints ### React Native Testing Jest + Bun's Content-Addressable Store (`.bun/`) sind strukturell inkompatibel für RN Rendering-Tests. `jest-expo` setzt `setupFiles` mit absoluten `.bun/` Pfaden — kein Resolver oder `transformIgnorePatterns` kann das abfangen. **Entscheidung:** `bun:test` für Business Logic (Stores, Hooks, Utils, Services). Rendering wird via Expo Go verifiziert. Rendering-Tests werden nachgereicht wenn Bun/jest-expo das nativ lösen. ### Web App (`apps/web`) TanStack Router generiert `routeTree.gen.ts` erst bei `vite dev`. `check-types` für web daher nur nach einmaligem dev-Run aussagekräftig. ### Better Auth Mobile Setup - `bearer` Plugin ist Pflicht in `packages/auth/src/index.ts` - Mobile Clients nutzen `Authorization: Bearer ` Header - Ohne `bearer` Plugin: `getSession()` gibt `null` zurück für alle Mobile Requests — stiller Auth-Fehler, alle Requests landen als 401 ### Expo Router — Redirect Pattern (FINAL) **FALSCH:** Session-Guards in mehreren Layouts gleichzeitig → Ping-Pong Loop zwischen `(auth)` und `(app)` Layout **RICHTIG:** - `index.tsx`: statischer `` — kein useSession, kein useEffect - `(auth)/_layout.tsx`: **KEIN Guard**, nur Stack-Definition - `(app)/_layout.tsx`: **EINZIGER Guard** - kein session → `/(auth)/login` - kein householdId → `/(auth)/onboarding` - sonst: render children Ein Guard, eine Quelle der Wahrheit. ### Household / Organization Bridge - `households.id === Better Auth organization.id` (gleiche UUID) - Reihenfolge beim Onboarding: 1. `organization.create()` → `organizationId` 2. `INSERT INTO households { id: organizationId, ... }` (via `/api/households/setup`) 3. `seedDefaultCategories(organizationId)` - Kein harter DB-FK von `households` zu `organization` — application-level check reicht, Better Auth Schema ist extern ## Projektstand | Phase | Status | Details | |-------|--------|---------| | Phase 1 — Foundation | ✅ | Monorepo, DB Schema, Shared Types, Stubs | | Phase 2 — Auth Flow | ✅ | Better Auth, Organization Plugin, Apple, Auth Screens | | Phase 3 — Transactions Full Stack | ✅ | 9 API Tests, 7 Mobile Tests, Tenant Isolation bestätigt | | Phase 4 — Dashboard | ⬜ | | | Phase 5 — Urlaubsbudget | ⬜ | | | Phase 6 — OCR Scanner | ⬜ | | | Phase 7 — Einkaufsliste (WebSockets) | ⬜ | | ## Skills Projekt-spezifische Skills unter `apps/api/.agents/skills/` (noch leer, wird befüllt). @apps/api/.agents/skills/