# 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 <token>` 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 `<Redirect href="/(auth)/login" />` — 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/
