docs: add Sub-Dokumentation section to AGENTS.md and CLAUDE.md for improved documentation structure

- Introduced a new section detailing the Single Source of Truth for various components, linking to their respective CLAUDE.md files.
- Enhanced clarity on documentation organization across different areas of the project.
This commit is contained in:
2026-03-31 21:12:26 +02:00
parent 4723fdca16
commit 1e99251506
10 changed files with 669 additions and 0 deletions

View File

@@ -3,3 +3,18 @@
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices. This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.
<!-- END:nextjs-agent-rules --> <!-- END:nextjs-agent-rules -->
## Sub-Dokumentation
Jeder Ordner hat eine eigene CLAUDE.md als Single Source of Truth. Vor Änderungen immer das zugehörige Dok lesen.
| Bereich | Dok |
|---------|-----|
| Convex Backend | [convex/CLAUDE.md](convex/CLAUDE.md) |
| Canvas-Engine | [components/canvas/CLAUDE.md](components/canvas/CLAUDE.md) |
| Design System (ShadCN) | [components/ui/CLAUDE.md](components/ui/CLAUDE.md) |
| Billing & Credits UI | [components/billing/CLAUDE.md](components/billing/CLAUDE.md) |
| Dashboard | [components/dashboard/CLAUDE.md](components/dashboard/CLAUDE.md) |
| Next.js Routing | [app/CLAUDE.md](app/CLAUDE.md) |
| Utilities & Shared Logic | [lib/CLAUDE.md](lib/CLAUDE.md) |
| Custom Hooks | [hooks/CLAUDE.md](hooks/CLAUDE.md) |

View File

@@ -1,5 +1,22 @@
@AGENTS.md @AGENTS.md
## Sub-Dokumentation
Jeder Ordner hat eine eigene CLAUDE.md als Single Source of Truth:
| Bereich | Dok |
|---------|-----|
| Convex Backend | [convex/CLAUDE.md](convex/CLAUDE.md) |
| Canvas-Engine | [components/canvas/CLAUDE.md](components/canvas/CLAUDE.md) |
| Design System (ShadCN) | [components/ui/CLAUDE.md](components/ui/CLAUDE.md) |
| Billing & Credits UI | [components/billing/CLAUDE.md](components/billing/CLAUDE.md) |
| Dashboard | [components/dashboard/CLAUDE.md](components/dashboard/CLAUDE.md) |
| Next.js Routing | [app/CLAUDE.md](app/CLAUDE.md) |
| Utilities & Shared Logic | [lib/CLAUDE.md](lib/CLAUDE.md) |
| Custom Hooks | [hooks/CLAUDE.md](hooks/CLAUDE.md) |
---
## Design Context ## Design Context
### Users ### Users

91
app/CLAUDE.md Normal file
View File

@@ -0,0 +1,91 @@
# app/ — Next.js Routing & Layouts
Next.js 16 App Router. Vor Änderungen: Relevante Guides in `node_modules/next/dist/docs/` lesen — Breaking Changes gegenüber früheren Versionen möglich.
---
## Route-Struktur
```
app/
├── layout.tsx ← Root Layout (Fonts, Providers, Sentry, Analytics)
├── page.tsx ← Landing / Redirect
├── globals.css ← Tailwind v4 + Design-Tokens
├── (app)/ ← Authentifizierte App-Routen
│ ├── canvas/[canvasId]/ ← Canvas-Editor
│ │ └── page.tsx
│ └── settings/
│ └── billing/ ← Billing-Einstellungen
├── auth/ ← Auth-Routen (Better Auth)
│ ├── [path]/ ← Catch-all für Better Auth Flows
│ ├── sign-in/
│ └── sign-up/
├── api/
│ ├── auth/[...all]/ ← Better Auth API Handler
│ └── pexels-video/ ← Pexels Video Proxy
└── dashboard/ ← Dashboard nach Login
```
---
## Root Layout (`layout.tsx`)
Server Component. Initialisiert:
1. **Font:** `Manrope` via `next/font/google` → CSS-Variable `--font-sans`
2. **Auth:** `getToken()` + `getAuthUser()` für SSR-seitigen Convex-Token
3. **Sentry:** `Sentry.setUser()` direkt im Layout (serverseitig)
4. **Analytics:** Rybbit-Script via `<script>` Tag (`rybbit.matthias.lol`)
5. **Providers:** `<Providers initialToken={...}>` — Convex + Theme + Auth
6. **InitUser:** `<InitUser />` — Ruft `api.credits.initBalance` beim ersten Login auf
---
## Provider-Pattern
```tsx
// components/providers.tsx
<ConvexProvider client={convex}>
<ThemeProvider>
<AuthProvider>
{children}
</AuthProvider>
</ThemeProvider>
</ConvexProvider>
```
`initialToken` wird server-seitig aus Better Auth ausgelesen und an Convex übergeben — verhindert einen Auth-Flash beim ersten Render. Mit `initialToken` springt `isAuthenticated` sofort auf `true` ohne Loading-Phase.
---
## Auth-Flow
**Better Auth** (self-hosted, open-source). Konfiguration in `lib/auth.ts` (Server) und `lib/auth-client.ts` (Client).
- Server-Helper: `getAuthUser()`, `getToken()` aus `lib/auth-server.ts`
- Client-Helper: `authClient` aus `lib/auth-client.ts`
- Convex-Integration: `convex/auth.config.ts` + `convex/auth.ts`
- Trusted Origins: `https://app.lemonspace.io`, `http://localhost:3000`
---
## Route Groups
`(app)/` — Klammer-Gruppen teilen kein eigenes Layout-Segment, ermöglichen aber eigene `layout.tsx` für Auth-Guards. Alle eingeloggten Routen liegen hier.
---
## API-Routen
- `api/auth/[...all]` — Better Auth catch-all Handler. Nicht anfassen ohne Better Auth Docs zu lesen.
- `api/pexels-video` — Server-seitiger Proxy für Pexels-Video-Anfragen (API-Key bleibt serverseitig).
---
## env-Variablen (Next.js-Seite)
```
BETTER_AUTH_SECRET
BETTER_AUTH_URL
NEXT_PUBLIC_CONVEX_URL
```

View File

@@ -0,0 +1,50 @@
# components/billing/ — Billing & Subscription UI
UI-Komponenten für Credit-Anzeige, Subscription-Management und Top-Up.
---
## Dateien
| Datei | Zweck |
|-------|-------|
| `pricing-cards.tsx` | Tier-Vergleich (Free / Starter / Pro / Max) |
| `manage-subscription.tsx` | Aktuelle Subscription verwalten (Upgrade, Kündigung) |
| `topup-panel.tsx` | Credit-Nachkauf-UI (fixe Pakete + Custom-Betrag) |
---
## Tier-Struktur
Tiers und ihre Credit-Mengen sind in `convex/credits.ts → TIER_CONFIG` definiert. Die Billing-UI liest diese Werte über Convex-Queries — keine doppelte Konfiguration hier.
```
Free: 50 Cr/Monat (€0)
Starter: 400 Cr/Monat (€8)
Pro: 3.300 Cr/Monat (€59)
Max: 6.700 Cr/Monat (€119)
```
**1 Credit = €0,01** — Diese Umrechnung überall konsistent verwenden.
---
## Payment-Integration
**Polar.sh** ist der aktuelle Payment-Provider (MoR, VAT-Handling). Webhooks landen in `convex/http.ts``convex/polar.ts`.
> Hinweis: `convex/schema.ts` enthält noch `lemonSqueezySubscriptionId`-Felder — Überbleibsel einer früheren Integration. Lemon Squeezy ist nicht mehr aktiv. Polar-Felder (`polarSubscriptionId`) sind maßgeblich.
---
## Credit-Balance im Dashboard
Die Credit-Balance wird auch in `components/dashboard/credit-overview.tsx` angezeigt. Beide Komponenten nutzen dieselbe Convex-Query (`api.credits.getBalance`).
---
## Konventionen
- Tier-Upgrades immer über Polar-Checkout (kein direktes Schreiben in `subscriptions`-Tabelle)
- `topUp` Mutation (`convex/credits.ts`) für Credit-Nachkauf aufrufen
- Monatliches Top-Up-Limit pro Tier beachten (siehe `TIER_CONFIG.topUpLimit`)

137
components/canvas/CLAUDE.md Normal file
View File

@@ -0,0 +1,137 @@
# components/canvas/ — Canvas-Engine
Der Canvas ist das Herzstück von LemonSpace. Er basiert auf `@xyflow/react` (React Flow) und synchronisiert seinen Zustand bidirektional mit Convex.
---
## Architektur
```
app/(app)/canvas/[canvasId]/page.tsx
└── <Canvas canvasId={...} /> ← components/canvas/canvas.tsx
├── <ReactFlowProvider>
│ └── <CanvasInner> ← Haupt-Komponente (2800 Zeilen)
│ ├── Convex useQuery ← Realtime-Sync
│ ├── nodeTypes Map ← node-types.ts
│ ├── localStorage Cache ← canvas-local-persistence.ts
│ └── Panel-Komponenten
└── Context Providers
```
**`canvas.tsx`** ist die zentrale Datei. Sie enthält die gesamte State-Management-Logik, Convex-Mutations, Optimistic Updates und Event-Handler. Sehr groß — vor Änderungen immer den genauen Abschnitt lesen.
---
## Convex ↔ React Flow Mapping
Convex und React Flow verwenden unterschiedliche Datenmodelle. Das Mapping liegt in `lib/canvas-utils.ts`:
| Richtung | Funktion |
|----------|----------|
| Convex Node → RF Node | `convexNodeToRF(doc)` |
| Convex Edge → RF Edge | `convexEdgeToRF(doc)` |
| Edge + Glow | `convexEdgeToRFWithSourceGlow(edge, sourceType, colorMode)` |
| StorageId → URL merge | `convexNodeDocWithMergedStorageUrl(node, urlMap, prevMap)` |
**Wichtig:** Convex speichert `positionX` / `positionY` als separate Felder. React Flow erwartet `position: { x, y }`. Niemals RF-Node-Objekte direkt in Convex schreiben.
**Status-Injection:** `convexNodeToRF` schreibt `_status`, `_statusMessage` und `retryCount` in `data`, damit Node-Komponenten darauf zugreifen können ohne das Node-Dokument direkt zu kennen.
**URL-Caching:** Images mit `storageId` werden über einen batch-Storage-URL-Query aufgelöst (`urlByStorage`-Map). Die vorherige URL wird in `previousDataByNodeId` gecacht, um Flackern beim Reload zu vermeiden.
---
## Node-Typen (Phase 1)
Alle registrierten Node-Typen in `node-types.ts`:
| Typ | Komponente | Kategorie | Handles |
|-----|-----------|-----------|---------|
| `image` | `ImageNode` | Quelle | source (default), target (default) |
| `text` | `TextNode` | Quelle | source (default), target (default) |
| `prompt` | `PromptNode` | KI-Ausgabe | source: `prompt-out`, target: `image-in` |
| `ai-image` | `AiImageNode` | KI-Ausgabe | source: `image-out`, target: `prompt-in` |
| `group` | `GroupNode` | Layout | source (default), target (default) |
| `frame` | `FrameNode` | Layout | source: `frame-out`, target: `frame-in` |
| `note` | `NoteNode` | Layout | source (default), target (default) |
| `compare` | `CompareNode` | Layout | source: `compare-out`, targets: `left`, `right` |
| `asset` | `AssetNode` | Quelle | source (default), target (default) |
| `video` | `VideoNode` | Quelle | source (default), target (default) |
> `nodeTypes`-Map **muss** außerhalb jeder React-Komponente definiert sein (sonst re-rendert React Flow bei jedem Render alle Nodes).
### Default-Größen (`lib/canvas-utils.ts → NODE_DEFAULTS`)
```
image: 280 × 200 prompt: 288 × 220
text: 256 × 120 ai-image: 320 × 408
group: 400 × 300 frame: 400 × 300
note: 208 × 100 compare: 500 × 380
```
---
## Node-Status-Modell
```
idle → analyzing → clarifying → executing (retry N/2) → done
→ error
```
Status + `statusMessage` werden direkt am Node angezeigt. Kein globales Loading-Banner. Bei `error` zeigt `statusMessage` die Kategorie: `Credits: ...`, `Timeout: ...`, `Provider: ...` etc.
---
## Edge-Glow-System
Jede Edge bekommt einen `drop-shadow`-Filter entsprechend dem Quell-Node-Typ. Farben in `lib/canvas-utils.ts → SOURCE_NODE_GLOW_RGB`:
- `prompt`, `ai-image` → Violett (139, 92, 246)
- `image`, `text`, `note` → Teal (13, 148, 136)
- `frame` → Orange (249, 115, 22)
- `group`, `compare` → Grau (100, 116, 139)
Compare-Node hat zusätzlich Handle-spezifische Farben (`left` → Blau, `right` → Smaragd).
---
## Optimistic Updates & Local Persistence
**Optimistic Prefix:** Temporäre Nodes/Edges erhalten IDs mit `optimistic_` / `optimistic_edge_`-Prefix. Sie werden durch echte Convex-IDs ersetzt sobald die Mutation abgeschlossen ist.
**localStorage-Cache** (`lib/canvas-local-persistence.ts`): Snapshot + Ops-Queue pro Canvas-ID.
- Key-Schema: `lemonspace.canvas:snapshot:v1:<canvasId>` und `lemonspace.canvas:ops:v1:<canvasId>`
- Snapshot = letzter bekannter State (Nodes + Edges) für schnellen initialen Render
- Ops-Queue = ausstehende Mutations (Recovery bei Verbindungsabbruch, Konzept — noch nicht vollständig implementiert)
---
## Panel-Komponenten
| Datei | Zweck |
|-------|-------|
| `canvas-toolbar.tsx` | Werkzeug-Leiste (Select, Pan, Zoom-Controls) |
| `canvas-app-menu.tsx` | App-Menü (Einstellungen, Logout, Canvas-Name) |
| `canvas-sidebar.tsx` | Node-Palette (linke Seite) |
| `canvas-command-palette.tsx` | Cmd+K Command Palette |
| `canvas-connection-drop-menu.tsx` | Kontext-Menü beim Loslassen einer Verbindung |
| `canvas-node-template-picker.tsx` | Node aus Template einfügen |
| `canvas-placement-context.tsx` | Context für Drag-and-Drop-Platzierung |
| `asset-browser-panel.tsx` | Freepik/Stock-Asset-Browser |
| `video-browser-panel.tsx` | Video-Asset-Browser |
| `canvas-user-menu.tsx` | User-Avatar und Menü |
| `credit-display.tsx` | Credit-Balance Anzeige in der Toolbar |
| `export-button.tsx` | Export-Button mit Format-Auswahl |
| `connection-banner.tsx` | Offline-Banner bei Convex-Verbindungsverlust |
| `custom-connection-line.tsx` | Angepasste temporäre Verbindungslinie |
---
## Wichtige Gotchas
- **`data.url` vs `storageId`:** Node-Komponenten erhalten `data.url` (aufgelöste HTTP-URL), nicht `storageId` direkt. Die URL wird von `convexNodeDocWithMergedStorageUrl` injiziert. Bei neuen Node-Typen mit Bild immer diesen Flow prüfen.
- **Min-Zoom:** `CANVAS_MIN_ZOOM = 0.5 / 3` — dreimal weiter raus als React-Flow-Default.
- **Parent-Nodes:** `parentId` zeigt auf einen Group- oder Frame-Node. React Flow erwartet, dass Parent-Nodes vor Child-Nodes in der `nodes`-Array stehen.
- **Bridge-Edges:** Beim Löschen eines mittleren Nodes werden Kanten automatisch neu verbunden (`computeBridgeCreatesForDeletedNodes` aus `lib/canvas-utils.ts`).
- **`"null"`-Handles:** Convex kann `"null"` als String speichern. `convexEdgeToRF` sanitized Handles: `"null"``undefined`.

View File

@@ -0,0 +1,39 @@
# components/dashboard/ — Dashboard
UI-Komponenten für die Startseite nach dem Login.
---
## Dateien
| Datei | Zweck |
|-------|-------|
| `canvas-card.tsx` | Karte für einen Canvas in der Übersicht (Thumbnail, Name, Datum) |
| `credit-overview.tsx` | Monatsverbrauch und verfügbare Credits als Balken-Visualisierung |
| `recent-transactions.tsx` | Liste der letzten Credit-Transaktionen |
---
## Datenquellen
Alle Daten kommen aus Convex-Queries via `useAuthQuery` (aus `hooks/use-auth-query.ts`):
| Komponente | Query |
|-----------|-------|
| `canvas-card.tsx` | `api.canvases.list` |
| `credit-overview.tsx` | `api.credits.getBalance`, `api.credits.getUsageStats` |
| `recent-transactions.tsx` | `api.credits.getRecentTransactions` |
---
## Layout-Seite
`app/dashboard/page.tsx` — Server Component, rendert Dashboard-Layout mit den Komponenten oben.
---
## Konventionen
- Kein lokaler State für Server-Daten — alles via Convex-Subscriptions (reaktiv)
- `useAuthQuery` statt `useQuery` verwenden, um Auth-Races zu vermeiden (skippt Query bis Token bereit ist)
- Canvas-Thumbnails sind optional (`thumbnail?: Id<"_storage">`) — Fallback-State immer behandeln

51
components/ui/CLAUDE.md Normal file
View File

@@ -0,0 +1,51 @@
# components/ui/ — Design System
ShadCN-Komponenten mit LemonSpace-spezifischen Anpassungen. Neue UI-Primitive immer hier ablegen.
---
## Stack
- **ShadCN UI** — Komponentenbibliothek (copy-paste, nicht installiertes Paket)
- **Tailwind CSS v4** — Utility-first, config in `globals.css` (kein `tailwind.config.ts` in v4)
- **Manrope** — Primäre Schrift (`--font-sans`), geladen via `next/font/google`
---
## Design-Token-Konventionen
Tokens folgen dem Manifest: **Teal-Primary, warme Neutraltöne (Sand/Beige), Zitronengelb als Akzent**.
```css
/* Primärfarbe */
--primary: teal /* Hauptaktionen, Links, aktive States */
/* Neutraltöne — immer warm tönen */
--background: warm-white /* Nie kaltes #fff oder gray-50 */
--muted: sand/beige /* Sekundäre Hintergründe */
--border: warm-gray /* Niemals kaltes gray-200 */
/* Akzentfarbe */
--accent: lemon-yellow /* Sparsam einsetzen — Highlights, Badges */
```
**Anti-Pattern:** Kalte Grautöne (`gray-100`, `slate-200`) ohne Wärme-Anpassung. Immer Richtung Sand/Beige tönen.
---
## Animationen
Übergänge mit **exponentiellem Easing** (`ease-out` / `cubic-bezier`). Keine linearen Transitions.
```css
transition: all 200ms cubic-bezier(0.16, 1, 0.3, 1); /* expo-out */
```
---
## Neue Komponenten
1. ShadCN-Komponente mit `npx shadcn@latest add <name>` hinzufügen
2. In `components/ui/` ablegen (automatisch durch ShadCN-CLI)
3. Anpassungen direkt in der Komponente — keine separaten Override-Dateien
4. Token-Konventionen aus `globals.css` verwenden, keine Hardcoded-Farben

123
convex/CLAUDE.md Normal file
View File

@@ -0,0 +1,123 @@
# convex/ — Backend-Schicht
Convex ist das vollständige Backend von LemonSpace: Datenbank, Realtime-Subscriptions, File Storage und Background-Job-Scheduler in einem. Kein separates API-Layer notwendig.
---
## Dateien im Überblick
| Datei | Zweck |
|-------|-------|
| `schema.ts` | Einzige Wahrheitsquelle für alle Tabellen und Typen |
| `ai.ts` | KI-Bildgenerierungs-Pipeline |
| `credits.ts` | Credit-System: Balance, Reservation+Commit, Tier-Config |
| `nodes.ts` | CRUD für Canvas-Nodes |
| `edges.ts` | CRUD für Canvas-Edges |
| `canvases.ts` | CRUD für Canvases |
| `openrouter.ts` | OpenRouter-HTTP-Client + Modell-Config (Backend) |
| `auth.ts` | Better Auth Integration |
| `helpers.ts` | `requireAuth()` — von allen Queries/Mutations genutzt |
| `polar.ts` | Polar.sh Webhook-Handler (Subscriptions) |
| `pexels.ts` | Pexels Stock-Bilder API |
| `freepik.ts` | Freepik Asset-Browser API |
| `storage.ts` | Convex File Storage Helpers |
| `export.ts` | Canvas-Export-Logik |
| `http.ts` | HTTP-Endpunkte (Webhooks) |
---
## Schema (`schema.ts`)
Alle Node-Typen sind in zwei Validators definiert: `phase1NodeTypes` (aktiv) und `nodeType` (alle Phasen). Phase-2/3-Typen werden im Schema vordeklariert, um spätere Schema-Migrationen zu vermeiden — die UI filtert nach Phase.
### Tabellen
**`canvases`** — Canvas pro User. Index: `by_owner`, `by_owner_updated`.
**`nodes`** — Alle Nodes eines Canvas. Felder: `type`, `positionX/Y`, `width`, `height`, `status` (`idle|analyzing|clarifying|executing|done|error`), `statusMessage`, `retryCount`, `data` (v.any()), `parentId`, `zIndex`. Index: `by_canvas`, `by_canvas_type`, `by_parent`.
> `data` ist `v.any()` — Typ-Safety läuft über den `type`-Discriminator + Zod im Frontend. Die Node-Data-Shapes sind in `schema.ts` dokumentiert (`imageNodeData`, `promptNodeData`, etc.).
**`edges`** — Verbindungen zwischen Nodes. Index: `by_canvas`, `by_source`, `by_target`.
**`creditBalances`** — Pro User: `balance`, `reserved`, `monthlyAllocation`. `available = balance - reserved` (computed, nicht gespeichert).
**`creditTransactions`** — Jede Credit-Bewegung. Types: `subscription | topup | usage | reservation | refund`. Status: `committed | reserved | released | failed`.
**`subscriptions`** — Aktive Subscription. Tier: `free | starter | pro | max | business`.
**`dailyUsage`** — Täglicher Zähler pro User für Abuse-Prevention. Key: `userId + date (ISO)`.
---
## AI-Pipeline (`ai.ts`)
```
generateImage (action, public)
→ checkAbuseLimits (internalMutation)
→ reserve (mutation, wenn INTERNAL_CREDITS_ENABLED=true)
→ markNodeExecuting (internalMutation)
→ scheduler.runAfter(0, processImageGeneration) ← Background-Job
processImageGeneration (internalAction)
→ generateAndStoreImage (internalAction)
→ generateImageWithAutoRetry (lokal, max 2 Retries)
→ generateImageViaOpenRouter
→ ctx.storage.store(blob)
→ finalizeImageSuccess (internalMutation)
→ commitInternal (credits)
[Fehler] → releaseInternal (credits)
→ finalizeImageFailure (internalMutation)
[finally] → decrementConcurrency
```
**Wichtig:** `generateImage` gibt `{ queued: true }` zurück, sobald der Background-Job geplant ist. Der Node wechselt sofort auf `status: "executing"`. Der eigentliche API-Call läuft asynchron.
**Retry-Logik:** Max. 2 Retries (`MAX_IMAGE_RETRIES = 2`). Backoff: `min(1500ms, 400ms * retryCount)`. Retryable: `provider` (5xx, 408, 429), `timeout`, `transient`. Nicht retryable: `credits`, `policy`, unbekannte 4xx.
**env-Flags:**
- `INTERNAL_CREDITS_ENABLED=true` — Aktiviert Reservation+Commit. Bei `false` wird nur Abuse-Prevention geprüft.
- `OPENROUTER_API_KEY` — Pflicht.
---
## Credit-System (`credits.ts`)
### Tier-Konfiguration (`TIER_CONFIG`)
| Tier | Credits/Monat | Daily Cap | Concurrency | Premium-Modelle |
|------|--------------|-----------|-------------|-----------------|
| free | 50 | 10 | 1 | nein |
| starter | 400 | 50 | 2 | ja |
| pro | 3.300 | 200 | 2 | ja |
| max | 6.700 | 500 | 2 | ja |
**1 Credit = €0,01** (Euro-Cent-Einheit durchgängig in DB und UI).
### Reservation+Commit-Flow
1. `reserve()` — Prüft `available >= estimatedCost`, Daily Cap, Concurrency. Erhöht `reserved` + `dailyUsage`. Gibt `transactionId` zurück.
2. Job läuft...
3. `commitInternal()` — Zieht `actualCost` von `balance` ab, gibt `estimatedCost` aus `reserved` frei. Schreibt `type: "usage"`.
4. Bei Fehler: `releaseInternal()` — Gibt `estimatedCost` aus `reserved` frei. `generationCount` bleibt erhöht (Versuch zählt).
**env-Flag:** `ALLOW_TEST_CREDIT_GRANT=true` — Aktiviert `grantTestCredits` Mutation (nur Dev/Staging).
---
## Auth (`helpers.ts`)
```typescript
requireAuth(ctx) // → { userId: string }
```
Wirft bei unauthentifiziertem Zugriff. Wird von allen Queries und Mutations genutzt, die User-Daten berühren.
---
## Konventionen
- `internalMutation` / `internalAction` — Nur von anderen Convex-Funktionen aufrufbar, nicht direkt vom Client.
- `mutation` / `query` / `action` — Öffentlich, direkt vom Frontend nutzbar.
- Alle User-Daten sind über `userId` (Better Auth User ID, kein Convex-eigenes User-Dokument) indiziert.
- Schema-Migrationen: `npx convex dev` erkennt Breaking Changes automatisch. Bei `v.any()`-Feldern gibt es keine automatische Migration — Datensätze müssen manuell/scriptmäßig migriert werden.

39
hooks/CLAUDE.md Normal file
View File

@@ -0,0 +1,39 @@
# hooks/ — Custom React Hooks
Geteilte React-Hooks. Nur client-side (`"use client"`).
---
## Hooks im Überblick
### `use-auth-query.ts`
```typescript
useAuthQuery(query, ...args)
```
Wrapper um Convex `useQuery`, der automatisch `"skip"` setzt wenn der Auth-Token noch nicht bereit ist. Verhindert `Unauthenticated`-Fehler bei Queries mit `requireAuth` im Backend.
**Warum nicht direkt `useQuery`?** Ohne `initialToken` würde Convex kurz eine unauthentifizierte Query feuern und eine Error-Toast anzeigen. Mit `useAuthQuery` wird gewartet bis `isAuthenticated === true`.
**Wann nutzen:** Immer wenn eine Convex-Query `requireAuth` aufruft. Für öffentliche Queries ist normales `useQuery` in Ordnung.
---
### `use-centered-flow-node-position.ts`
Berechnet die Canvas-Position für einen neuen Node, sodass er im aktuellen Viewport-Zentrum erscheint. Wird beim Einfügen eines Nodes aus der Palette oder Command Palette genutzt.
---
### `use-debounced-callback.ts`
Standard-Debounce-Hook. Wird für teure Operationen wie Canvas-Snapshots und Convex-Mutations beim Resizen/Bewegen von Nodes verwendet.
---
## Konventionen
- Hooks immer mit `use-` Prefix im Dateinamen
- Nur wiederverwendbare Hooks hier — canvas-spezifische Inline-Logik bleibt in `canvas.tsx`
- Kein direkter Convex-Zugriff in Hooks wenn möglich — Queries/Mutations von der aufrufenden Komponente übergeben lassen

107
lib/CLAUDE.md Normal file
View File

@@ -0,0 +1,107 @@
# lib/ — Utilities & Shared Logic
Geteilte Hilfsfunktionen, Typ-Definitionen und Konfiguration. Keine React-Komponenten — nur reines TypeScript.
---
## Dateien im Überblick
| Datei | Zweck |
|-------|-------|
| `canvas-utils.ts` | Convex↔React Flow Adapter, Edge-Glow, Node-Defaults, Bridge-Edges |
| `canvas-node-catalog.ts` | Vollständige Node-Taxonomie (alle Phasen, Kategorien, Phase-Flags) |
| `canvas-node-templates.ts` | Default-Daten für neue Nodes (beim Einfügen aus Palette) |
| `ai-models.ts` | Client-seitige Modell-Definitionen (muss mit `convex/openrouter.ts` in sync bleiben) |
| `image-formats.ts` | Aspect-Ratio-Strings, Node-Chrome-Höhen (`AI_IMAGE_NODE_HEADER_PX` etc.) |
| `auth.ts` | Better Auth Server-Instanz |
| `auth-server.ts` | Server-Helper: `getAuthUser()`, `getToken()` |
| `auth-client.ts` | Client-Helper: `authClient` |
| `canvas-local-persistence.ts` | localStorage-Cache für Canvas-Snapshots und Op-Queue |
| `toast.ts` | Toast-Utility-Wrapper |
| `toast-messages.ts` | Typisierte Toast-Message-Definitionen (`msg`, `CanvasNodeDeleteBlockReason`) |
| `ai-errors.ts` | Error-Kategorisierung und User-facing Fehlermeldungen |
| `pexels-types.ts` | TypeScript-Typen für Pexels-API-Responses |
| `polar-products.ts` | Polar.sh Produkt-IDs und Tier-Mapping |
| `rate-limit.ts` | Rate-Limiting-Utilities (Redis-backed) |
| `redis.ts` | Redis-Client-Initialisierung |
| `topup-calculator.ts` | Bonus-Staffel-Berechnung für Credit-Top-Ups |
| `format-time.ts` | Zeitformatierung (relative Zeitangaben) |
| `utils.ts` | `cn()` (clsx + tailwind-merge), allgemeine Utilities |
---
## `canvas-utils.ts` — Wichtigste Datei
Alle Adapter-Funktionen zwischen Convex-Datenmodell und React Flow. Details in `components/canvas/CLAUDE.md`.
**Kritische Exports:**
- `convexNodeToRF`, `convexEdgeToRF`, `convexEdgeToRFWithSourceGlow`
- `convexNodeDocWithMergedStorageUrl` — URL-Injection für Storage-Bilder
- `NODE_DEFAULTS` — Default-Größen und Daten per Node-Typ
- `NODE_HANDLE_MAP` — Handle-IDs pro Node-Typ
- `computeBridgeCreatesForDeletedNodes` — Kanten-Reconnect nach Node-Löschung
- `computeMediaNodeSize` — Dynamische Node-Größe basierend auf Bild-Dimensionen
---
## `canvas-node-catalog.ts` — Node-Taxonomie
Einzige Wahrheitsquelle für alle Node-Typen auf Client-Seite.
```typescript
NODE_CATALOG // Alle Nodes aller Phasen
NODE_CATEGORY_META // Label + Sortierung pro Kategorie
isNodePaletteEnabled // true wenn: implementiert + kein systemOutput + Template vorhanden
catalogEntriesByCategory() // Gruppiert für Sidebar-Rendering
```
**Kategorien:** `source`, `ai-output`, `transform`, `image-edit`, `control`, `layout`
Phase-2/3-Nodes haben `implemented: false` und `disabledHint`. Nie `implemented: true` setzen ohne zugehörige React-Flow-Komponente in `components/canvas/nodes/`.
---
## `ai-models.ts` — Sync-Pflicht
```typescript
// Muss identisch zu convex/openrouter.ts sein!
export const IMAGE_MODELS: AiModel[]
export const DEFAULT_MODEL_ID: string
```
**Achtung:** Diese Datei und `convex/openrouter.ts` müssen immer synchron gehalten werden. Bei neuen Modellen beide Dateien gleichzeitig aktualisieren. `creditCost` muss übereinstimmen — sonst stimmt die angezeigte Kostenvorschau nicht mit dem tatsächlichen Abzug überein.
---
## `canvas-local-persistence.ts` — localStorage-Cache
```typescript
readCanvasSnapshot(canvasId) // Letzten Snapshot laden
writeCanvasSnapshot(canvasId, {nodes, edges}) // Snapshot speichern
enqueueCanvasOp(canvasId, op) // Op in Queue schreiben
resolveCanvasOp(canvasId, opId) // Op aus Queue entfernen
readCanvasOps(canvasId) // Ausstehende Ops lesen
```
Key-Schema: `lemonspace.canvas:snapshot:v1:<id>` / `lemonspace.canvas:ops:v1:<id>`. Bei Version-Bumps (`SNAPSHOT_VERSION`, `OPS_VERSION`) werden alte Keys automatisch ignoriert.
---
## Auth-Helpers
```typescript
// Server (lib/auth-server.ts) — nur in Server Components / Route Handlers
getAuthUser() // → Better Auth User | null
getToken() // → Convex JWT Token | null (für initialToken im Root Layout)
// Client (lib/auth-client.ts)
authClient // Better Auth Client-Instanz für signIn, signUp, signOut etc.
```
---
## Konventionen
- Keine React-Imports in `lib/` — reines TypeScript
- `utils.ts` für generische Helpers (`cn`, `clamp`, etc.)
- Typen, die sowohl Frontend als auch Convex betreffen, gehören in `lib/`, nicht in `convex/`