Update LemonSpace Manifest to v2.1, enabling all 9 image models in OpenRouter with server-side tier enforcement. Enhance dashboard functionality with a bundled snapshot query and localStorage caching for improved performance and analytics. Introduce credits activity chart and optimize canvas graph queries for better data handling.

This commit is contained in:
Matthias
2026-04-08 14:03:16 +02:00
parent 87d78e4c99
commit a7eb2bc99c
8 changed files with 283 additions and 73 deletions

View File

@@ -1,6 +1,6 @@
# 🍋 LemonSpace — Produkt-Manifest
**v2.0 — April 2026**
**v2.1 — April 2026**
*Self-Hosted, Source-Available Creative Workspace*
@@ -129,7 +129,7 @@ Kompakt statt erschöpfend. Details wandern in eigene Architecture Decision Reco
|-------|-------------|--------|
| Backend | Convex (self-hosted). Bewusster Lock-in für Realtime, Storage, Jobs. Migrations-Pfad: Convex Cloud EU. | ✅ |
| Auth | Better Auth + Magic Link (via polar-sh/better-auth plugin) | ✅ |
| AI Layer | OpenRouter als primäre AI-Schicht. 9 Image-Modelle, Phase 1: nur Gemini 2.5 Flash aktiv. | ✅ |
| AI Layer | OpenRouter als primäre AI-Schicht. Alle 9 Image-Modelle aktiv, serverseitiges Tier-Enforcement, tier-aware Model Selector. | ✅ |
| Self-hosted KI | rembg, Real-ESRGAN, GFPGAN — kostenlos, separate Repos | ✅ |
| Payment | Polar.sh (MoR, VAT, Better Auth Plugin @polar-sh/better-auth) | ✅ |
| Credits | Reservation + Commit. Credit-Abstraktion (1 Cr = €0,01 OR intern). Markup: 2× Bild, 2,53× Agent. | ✅ |
@@ -160,7 +160,7 @@ Fokus heißt Nein sagen. Diese Features sind bewusst ausgeklammert, nicht verges
| Team-Features | Workspaces, Rollen, Rechte, Seat-Management — erst wenn Business-Tier validiert. |
| docker-compose.yml | Self-Hosting dokumentieren, aber nicht den Hosted-MVP verzögern. |
| E2E-Testing | Neubewertung bei Skalierung. |
| Modellauswahl-UI | Phase 1 nur ein Modell (Gemini 2.5 Flash). Auswahl-UI folgt in Phase 2. |
---
@@ -235,10 +235,11 @@ Priorisiert nach Abhängigkeiten. Jeder Schritt hat ein klares Artefakt.
| 6 | Better Auth + Polar + Credit-System | Login, Checkout, Balance-Tracking, Reservation+Commit | ✅ |
| 7 | Polar Webhook-Handling | Subscription-Events, automatische Credit-Zuweisung | ✅ |
| 8 | WebGL Image Pipeline | Adjustment-Nodes mit GLSL-Shadern | ✅ |
| 9 | Vollständige OpenRouter Integration | Alle 9 Modelle + Modellauswahl-UI | |
| 9 | Vollständige OpenRouter Integration | Alle 9 Modelle + Modellauswahl-UI | |
| 10 | Agent Node | Analyse, Clarification, Execution, Output | ☐ |
| 11 | Self-hosted KI-Services | rembg, Real-ESRGAN, GFPGAN | ☐ |
| 12 | docker-compose.yml + Setup-README | Self-Hosting-Anleitung | ☐ |
| 13 | Dashboard Snapshot + Analytics | Gebündelte Query, localStorage-Cache, Credits-Activity-Chart | ✅ |
---
@@ -257,4 +258,4 @@ Folgende Themen werden in eigenen Dokumenten vertieft. Das Manifest bleibt schla
---
*LemonSpace Manifest v2.0 — April 2026*
*LemonSpace Manifest v2.1 — April 2026*

View File

@@ -4,7 +4,7 @@
| Version | Status | Datum | Projekt |
|---------|--------|-------|---------|
| v2.0 | Draft | April 2026 | lemonspace.app |
| v2.1 | Draft | April 2026 | lemonspace.app |
---
@@ -27,6 +27,7 @@
| v1.4 | Bildbearbeitung: Neue Kategorie 4 „Bildbearbeitung" mit non-destruktivem Adjustment-Stack (zwischen Transformation und Steuerung). 4 Adjustment-Nodes (Kurven, Farbe, Licht, Detail) + Render-Node. Alle Operationen credit-frei (client-seitig via Canvas API / WebGL). Steuerung → Kat. 5, Canvas & Layout → Kat. 6. Phase 2. |
| v1.5 | Stage 3 Offline Sync: Local-First Canvas mit IndexedDB Queue, Optimistic Updates, ID-Remapping. Magic Link Auth via Better Auth Plugin. react-resizable-panels für Sidebar Resizing. Canvas Modularisierung, Dashboard Dialoge, Auth Race-Härtung.|
| v2.0 | **Phase-1-Umfang erweitert:** Video- und Asset-Nodes vorgezogen (Phase 2→1). Bildbearbeitungs-Nodes (Kurven, Farbe, Licht, Detail, Render) vorgezogen (Phase 2→1). Vollständige WebGL-basierte Image-Pipeline implementiert (`lib/image-pipeline/`). Node-Taxonomie hat 6 Kategorien mit 27 Node-Typen. Phase-1-Status-Tabelle aktualisiert. |
| v2.1 | **Alle 9 Image-Modelle aktiviert (Phase 2→1):** Vollständige OpenRouter Image Gen Integration mit serverseitigem Tier-Enforcement und modellspezifischen Request-Modalities. Tier-aware Model Selector im Prompt-Node. AI-Modularisierung: `ai_errors.ts`, `ai_node_data.ts`, `ai_retry.ts` aus `ai.ts` extrahiert. Dashboard Snapshot Cache (`convex/dashboard.ts`): Gebündelte Query mit localStorage-Cache (`lib/dashboard-snapshot-cache.ts`). Credits Activity Analytics: `lib/credits-activity.ts` + `CreditsActivityChart` (Recharts). Canvas Graph Query Cache (`convex/canvasGraph.ts` + `canvas-graph-query-cache.ts`): Performance-Optimierung durch separaten Graph-Endpunkt mit Optimistic Store. Neuer Hook `use-dashboard-snapshot.ts`. ShadCN Chart-Komponente (`components/ui/chart.tsx`) + Recharts 3.8. |
---
@@ -238,6 +239,7 @@ Agent Nodes sind ein spezieller Node-Typ auf dem Canvas. Sie fungieren als Smart
| Image Pipeline | WebGL + GLSL Shaders | Hardware-beschleunigte Bildverarbeitung im Browser |
| Offline Sync | IndexedDB + localStorage | Canvas-Sync-Queue, Snapshot-Persistenz, Optimistic Updates |
| Package Manager | pnpm | Je Repo |
| Charts / Visualization | Recharts + ShadCN Chart | v3.8.0 — Dashboard Credits Activity Chart |
### Zwei-Repo-Strategie
@@ -295,13 +297,23 @@ Dokumentierter Migrationspfad bei Skalierung: Convex Cloud mit EU-Standort. Conv
| Gemini 3 Pro Image | google/gemini-3-pro-image-preview | Multi-Image, 4K, bestes Text-Rendering | ~€0,080,15 |
| GPT-5 Image | openai/gpt-5-image | Instruction Following, Text in Bild | ~€0,100,20 |
### Aktuell aktiviertes Modell (Phase 1)
### Aktuell aktivierte Modelle (Phase 1)
| Modell | ID | Credits | Tier-Zugang |
|--------|-----|---------|-------------|
| Gemini 2.5 Flash Image | google/gemini-2.5-flash-image | 4 Cr | Alle Tiers |
Alle 9 Image-Modelle sind aktiviert. Server-seitiges Tier-Enforcement prüft `minTier` pro Modell. Der Prompt-Node bietet einen tier-aware Model Selector.
> Weitere Modelle werden in Phase 2 freigeschaltet (Modellauswahl-UI im Canvas).
| Modell | ID | Credits | Tier-Zugang | Modalities |
|--------|-----|---------|-------------|------------|
| Gemini 2.5 Flash | google/gemini-2.5-flash-image | 4 Cr | Alle Tiers | image + text |
| FLUX.2 Klein 4B | black-forest-labs/flux.2-klein-4b | 2 Cr | Alle Tiers | image only |
| Seedream 4.5 | bytedance-seed/seedream-4.5 | 5 Cr | Alle Tiers | image only |
| Gemini 3.1 Flash Image | google/gemini-3.1-flash-image-preview | 6 Cr | Alle Tiers | image + text |
| GPT-5 Image Mini | openai/gpt-5-image-mini | 8 Cr | Ab Starter | image + text |
| Riverflow V2 Fast | sourceful/riverflow-v2-fast | 9 Cr | Ab Starter | image only |
| Riverflow V2 Pro | sourceful/riverflow-v2-pro | 12 Cr | Ab Starter | image only |
| Gemini 3 Pro Image | google/gemini-3-pro-image-preview | 13 Cr | Ab Starter | image + text |
| GPT-5 Image | openai/gpt-5-image | 15 Cr | Ab Starter | image + text |
> **Request Modalities:** Modelle mit `image only` senden `modalities: ["image"]`, alle anderen `modalities: ["image", "text"]`. Dies wird in `convex/openrouter.ts` über das `requestModalities`-Feld gesteuert.
### Self-hosted Services
@@ -466,12 +478,15 @@ Credits = ROUND(API-Kosten × Markup ÷ Kurs). Agent-Calls haben höheren Markup
| Operation | Modell | API-Kosten | Markup | Credits | Tier-Zugang |
|-----------|--------|------------|--------|---------|-------------|
| Bildgenerierung (Standard) | Gemini 2.5 Flash Image | ~€0,04 | 2× | 4 Cr | Alle Tiers |
| Bildgenerierung (Budget) | FLUX.2 Klein 4B | ~€0,02 | 2× | 4 Cr | Alle Tiers |
| Bildgenerierung (Standard+) | Gemini 3.1 Flash Image | ~€0,06 | 2× | 12 Cr | Alle Tiers |
| Bildgenerierung (Premium) | GPT-5 Image Mini | ~€0,08 | 2× | 16 Cr | Ab Starter |
| Bildgenerierung (Ultra) | GPT-5 Image | ~€0,18 | 2× | 36 Cr | Ab Starter |
| Bildgen. (Pro Text/4K) | Riverflow V2 Pro | ~€0,33 | 1,5× | 50 Cr | Ab Starter |
| Bildgenerierung (Standard) | Gemini 2.5 Flash | ~€0,04 | 1× | 4 Cr | Alle Tiers |
| Bildgenerierung (Budget) | FLUX.2 Klein 4B | ~€0,02 | 1× | 2 Cr | Alle Tiers |
| Bildgenerierung (Standard) | Seedream 4.5 | ~€0,05 | 1× | 5 Cr | Alle Tiers |
| Bildgenerierung (Standard+) | Gemini 3.1 Flash Image | ~€0,06 | 1× | 6 Cr | Alle Tiers |
| Bildgenerierung (Premium) | GPT-5 Image Mini | ~€0,08 | 1× | 8 Cr | Ab Starter |
| Bildgenerierung (Premium) | Riverflow V2 Fast | ~€0,09 | 1× | 9 Cr | Ab Starter |
| Bildgenerierung (Premium) | Riverflow V2 Pro | ~€0,12 | 1× | 12 Cr | Ab Starter |
| Bildgenerierung (Premium) | Gemini 3 Pro Image | ~€0,13 | 1× | 13 Cr | Ab Starter |
| Bildgenerierung (Ultra) | GPT-5 Image | ~€0,15 | 1× | 15 Cr | Ab Starter |
| Agent Reasoning (leicht) | Claude Sonnet | ~€0,03 | 3× | 9 Cr | Ab Starter |
| Agent Reasoning (mittel) | Claude Sonnet | ~€0,06 | 2,5× | 15 Cr | Ab Starter |
| Agent-Run (komplex) | Multi-Step Workflow | ~€0,15 | 2,5× | 38 Cr | Ab Starter |
@@ -590,6 +605,12 @@ Agent Status: analyzing
| Video Browser | ✅ Erledigt |
| Connection Policy (Edge-Validierung) | ✅ Erledigt |
| Adjustment Presets (Built-in + User-defined) | ✅ Erledigt |
| Vollständige OpenRouter Image Gen (alle 9 Modelle + Tier-Enforcement) | ✅ Erledigt |
| Tier-aware Model Selector im Prompt-Node | ✅ Erledigt |
| Dashboard Snapshot Cache (gebündelte Query + localStorage) | ✅ Erledigt |
| Credits Activity Analytics (Recharts Chart) | ✅ Erledigt |
| Canvas Graph Query Cache (Performance-Optimierung) | ✅ Erledigt |
| AI Modularisierung (`ai_errors.ts`, `ai_retry.ts`, `ai_node_data.ts`) | ✅ Erledigt |
| docker-compose.yml + .env.example + Setup-README | ☐ Offen |
### Phase 2 — KI-Features
@@ -605,8 +626,6 @@ Agent Status: analyzing
| Task | Status |
|------|--------|
| Vollständige OpenRouter Image Gen Integration (alle 9 Modelle) | ☐ Offen |
| Experten-Modus: Modellauswahl-UI im Canvas AI Panel | ☐ Offen |
| OpenRouter Text/Reasoning Integration (Claude 3.5 Sonnet) | ☐ Offen |
| Agent Node: Analyse, Clarification, Execution, Output | ☐ Offen |
| Skeleton-Nodes: Platzierung nach Plan-Erstellung | ☐ Offen |
@@ -632,7 +651,7 @@ Agent Status: analyzing
| Versions-History | ☐ Offen |
| Weitere Agent Templates | ☐ Offen |
| Export-Funktionen (PNG, PDF, ZIP) | ☐ Offen |
| Performance-Optimierung für große Canvases | ☐ Offen |
| Performance-Optimierung für große Canvases | ✅ Erledigt |
---
@@ -646,7 +665,7 @@ Agent Status: analyzing
| Payment Provider | ✅ Polar (Merchant of Record, VAT-Handling) |
| Self-Hosting-Strategie | ✅ docker-compose.yml + .env.example + README |
| Convex Lock-in | ✅ Bewusst akzeptiert; Migrations-Pfad: Convex Cloud EU |
| OpenRouter Image-Modelle | ✅ 9 Modelle definiert, Phase 1: nur Gemini 2.5 Flash aktiv |
| OpenRouter Image-Modelle | ✅ Alle 9 Modelle aktiv mit serverseitigem Tier-Enforcement |
| Lizenz | ✅ BSL 1.1, 3 Jahre Change Date, Apache 2.0 |
| Repo-Strategie | ✅ Zwei unabhängige Repos, Auth-Cookie-Sharing |
| Job Queue | ✅ Convex native (Phase 1), externe Lösung bei Bedarf |
@@ -721,4 +740,4 @@ Die Software wird unter der Business Source License 1.1 (BSL 1.1) veröffentlich
---
*LemonSpace PRD v2.0 — April 2026*
*LemonSpace PRD v2.1 — April 2026*

View File

@@ -36,6 +36,8 @@ app/(app)/canvas/[canvasId]/page.tsx
| `canvas-delete-handlers.ts` | Hook für `onBeforeDelete`, `onNodesDelete`, `onEdgesDelete` inkl. Bridge-Edges |
| `canvas-reconnect.ts` | Hook für Edge-Reconnect (`onReconnectStart`, `onReconnect`, `onReconnectEnd`) |
| `canvas-media-utils.ts` | Media-Helfer wie `getImageDimensions(file)` |
| `use-canvas-data.ts` | Hook: Bündelt Canvas-Graph-Query, Storage-URL-Auflösung und Auth-State in einer einzigen Abstraktion |
| `canvas-graph-query-cache.ts` | Optimistic Store Helper für `canvasGraph.get` (getNodes, getEdges, setNodes, setEdges) |
---
@@ -231,6 +233,39 @@ Im **Light Mode** wird der eigentliche Edge-`stroke` ebenfalls aus dieser Akzent
---
## Canvas Graph Query Cache
Performance-Optimierung: Statt separater Queries für Nodes und Edges nutzt der Canvas eine einzige gebündelte Query (`canvasGraph.get`), die über einen Optimistic Store Layer läuft.
**Architektur:**
```
useCanvasData (use-canvas-data.ts)
├── canvasGraphQuery (canvasGraph.get) ← Einzelne gebündelte Query
├── canvasGraphQueryCache (Optimistic Store Helper)
│ ├── getCanvasGraphNodesFromQuery()
│ ├── getCanvasGraphEdgesFromQuery()
│ ├── setCanvasGraphNodesInQuery()
│ └── setCanvasGraphEdgesInQuery()
├── Storage URL Resolution (batchGetUrlsForCanvas)
└── Auth State (authClient.useSession + useConvexAuth)
```
**Vorteil:** Optimistic Updates (Node-Erstellung, Edge-Erstellung etc.) aktualisieren den Optimistic Store direkt, ohne auf die Server-Bestätigung warten zu müssen. Die separaten Node/Edge-Queries wurden durch diesen Ansatz abgelöst.
**`use-canvas-data.ts`:**
- Kapselt den gesamten Canvas-Datenfluss: Graph-Query → Storage-URLs → fertige Daten
- `shouldSkipCanvasQueries` verhindert API-Calls vor Auth-Ready
- `storageIdsForCanvas` extrahiert Storage-IDs aus Nodes und löst sie via `batchGetUrlsForCanvas` auf
- Development-Logging für Auth-State-Debugging
**`canvas-graph-query-cache.ts`:**
- Typisierter Zugriff auf den Convex Optimistic Local Store
- `canvasGraphQuery` — Typ-safe Reference auf `api.canvasGraph.get`
- `getCanvasGraphNodesFromQuery/EdgesFromQuery` — Lesen
- `setCanvasGraphNodesInQuery/EdgesInQuery` — Schreiben (für Optimistic Updates)
---
## 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.
@@ -244,3 +279,4 @@ Im **Light Mode** wird der eigentliche Edge-`stroke` ebenfalls aus dieser Akzent
- **Node-Taxonomie:** Alle Node-Typen sind in `lib/canvas-node-catalog.ts` definiert. Phase-2/3 Nodes haben `implemented: false` und `disabledHint`.
- **Video-Connection-Policy:** `video-prompt` darf **nur** mit `ai-video` verbunden werden (und umgekehrt). `text → video-prompt` ist erlaubt (Prompt-Quelle). `ai-video → compare` ist erlaubt.
- **Convex Generated Types:** `api.ai.generateVideo` wird u. U. nicht in `convex/_generated/api.d.ts` exportiert. Der Code verwendet `api as unknown as {...}` als Workaround. Ein `npx convex dev`-Zyklus würde die Typen korrekt generieren.
- **Canvas Graph Query:** Der Canvas nutzt `canvasGraph.get` (aus `convex/canvasGraph.ts`) statt separater `nodes.list`/`edges.list` Queries. Optimistic Updates laufen über `canvas-graph-query-cache.ts`.

View File

@@ -10,93 +10,123 @@ UI-Komponenten für die Startseite nach dem Login.
|-------|-------|
| `canvas-card.tsx` | Karte für einen Canvas in der Übersicht (Navigation, Umbenennen, Löschen mit Confirm-Dialog) |
| `credit-overview.tsx` | Monatsverbrauch und verfügbare Credits als Balken-Visualisierung |
| `credits-activity-chart.tsx` | Credits-Aktivitäts-Chart (Recharts AreaChart) mit Verbrauch, Aktivität und verfügbaren Credits |
| `recent-transactions.tsx` | Liste der letzten Credit-Transaktionen |
---
## Layout-Seite
`app/dashboard/page.tsx`Server Component, rendert Dashboard-Layout mit den Komponenten oben.
`app/dashboard/page-client.tsx`Client Component, rendert Dashboard mit gebündelter Snapshot-Query.
**Layout-Struktur:**
```
Dashboard
├── Header (User-Avatar, Name)
├── Quick Actions (Create Canvas, Search)
├── Credit Overview (Balance, Usage Bars)
├── Recent Transactions (List)
── Canvas Grid (Canvas Cards)
├── Header (Logo, Suche, User-Menü mit Theme-Wechsel)
├── Begrüßung (Name, Untertitel)
├── Credit-Übersicht (Balance, Usage Bars)
├── Arbeitsbereiche (Canvas Grid)
── Credits Verlauf + Transaktionen (zwei-spaltig)
│ ├── CreditsActivityChart (Recharts AreaChart)
│ └── Recent Transactions (List)
```
---
## Dashboard Snapshot Architecture
**Früher:** Separate Convex-Queries für Balance, Subscription, UsageStats, Transactions, Canvases → 5+ gleichzeitige Queries.
**Jetzt:** Gebündelte Snapshot-Query (`api.dashboard.getSnapshot`) + localStorage-Cache.
### Datenfluss
```
page-client.tsx
└── useDashboardSnapshot(userId)
├── Convex Query: api.dashboard.getSnapshot (gebündelt)
│ → Balance + Subscription + UsageStats + Transactions + Canvases
├── localStorage Cache: readDashboardSnapshotCache (12h TTL)
│ → Sofortige Anzeige aus Cache während Query lädt
└── Automatisches Cache-Write bei neuen Query-Daten
```
### Snapshot Cache (`lib/dashboard-snapshot-cache.ts`)
- **Key:** `lemonspace.dashboard:snapshot:v1:<userId>`
- **TTL:** 12 Stunden (DEFAULT_TTL_MS)
- **Version:** `CACHE_VERSION = 1` — Version-Bumps invalidieren automatisch
- **Cache-Invalidierung:** Bei Logout wird der Cache des vorherigen Users gelöscht (via `sessionStorage("ls-last-dashboard-user")`)
- Defensiv: Alle Storage-Zugriffe sind try-catch-gewrappt (Quota-Fehler, Disabled Storage etc.)
### Snapshot Query (`convex/dashboard.ts`)
- `getSnapshot` — Gebündelte Query, lädt alle Dashboard-Daten in einem Convex-Call
- Nutzt `optionalAuth` → Default-Werte bei fehlender Session (kein Error)
- Transaktions-Priorisierung via `prioritizeRecentCreditTransactions` (Usage > Reservation > Refund > Topup > Subscription)
---
## Datenquellen
Alle Daten kommen aus Convex-Queries via `useAuthQuery` (aus `hooks/use-auth-query.ts`):
| Komponente | Datenquelle |
|-----------|-------------|
| `credit-overview.tsx` | `dashboardSnapshot.balance`, `dashboardSnapshot.subscription`, `dashboardSnapshot.usageStats` |
| `credits-activity-chart.tsx` | `dashboardSnapshot.balance`, `dashboardSnapshot.recentTransactions` |
| `recent-transactions.tsx` | `dashboardSnapshot.recentTransactions` |
| `canvas-card.tsx` | `dashboardSnapshot.canvases` |
| Komponente | Query |
|-----------|-------|
| `canvas-card.tsx` | `api.canvases.list` |
| `credit-overview.tsx` | `api.credits.getBalance`, `api.credits.getUsageStats` |
| `recent-transactions.tsx` | `api.credits.getRecentTransactions` |
> Alle Daten kommen aus dem gebündelten Snapshot (`useDashboardSnapshot`). Keine separaten Queries mehr.
## Mutations
| Komponente | Mutation |
|-----------|----------|
| `canvas-card.tsx` | `api.canvases.update`, `api.canvases.remove` |
| `page-client.tsx` | `api.canvases.create` |
---
## Credits Activity Chart
Recharts-basiertes AreaChart (via ShadCN `ChartContainer`) mit drei Datenseries:
- **Usage** (gefüllt): Tatsächlicher Credit-Verbrauch pro Tag (committed usage-Transactions)
- **Activity** (gefüllt): Usage + aktive Reservationen pro Tag
- **Available** (Linie): Aktueller verfügbarer Credit-Stand als Referenzlinie
**Daten-Processing:** `lib/credits-activity.ts`
- `buildCreditsActivitySeries()` — Gruppiert Transactions nach Tag, aggregiert Usage/Activity
- `calculateUsageActivityDomain()` — Berechnet Y-Achsen-Domain mit Headroom
- `prioritizeRecentCreditTransactions()` — Sortiert Transactions nach Typ-Priorität
**Chart Config:** Teal für Usage, Accent-Foreground für Activity, Muted-Foreground für Available.
---
## 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)
- Kein lokaler State für Server-Daten — Snapshot kommt aus `useDashboardSnapshot`
- Dashboard zeigt sofort Cache-Daten an, während Live-Query lädt (`source: "cache" | "live" | "none"`)
- Canvas-Thumbnails sind optional (`thumbnail?: Id<"_storage">`) — Fallback-State immer behandeln
- Responsive Grid-Layout für Canvas-Cards (`grid-cols-1 md:grid-cols-2 lg:grid-cols-3`)
- Leere Zustände (keine Canvases, keine Transaktionen) mit nutzerfreundlichen Platzhaltern zeigen
---
## Features
### Canvas Cards
Jede Karte zeigt:
- Canvas-Thumbnail (wenn verfügbar)
- Canvas-Name (truncate)
- Letzte Änderungszeit (relative Formatierung)
- Klick → Öffnen im Canvas-Editor
- Menü → Umbenennen, Löschen
### Credit Overview
Zeigt:
- Aktueller Credit-Balance (z.B. "350 Credits")
- Verbrauchs-Balken (Progress Bar) für aktuellen Monat
- Monats-Details (vom `api.credits.getUsageStats`)
- Link zum Upgrade im Abo-Settings
### Recent Transactions
Liste der letzten Credit-Transaktionen mit:
- Transaktions-Typ (Subscription, Top-Up, Usage)
- Betrag (+/-)
- Beschreibung
- Datum (relative Formatierung)
- Responsive Grid-Layout für Canvas-Cards (`grid-cols-1 sm:grid-cols-3`)
- Leere Zustände (keine Canvases, keine Transaktionen, keine Chart-Daten) mit nutzerfreundlichen Platzhaltern zeigen
- Chart-Komponente nutzt ShadCN `ChartContainer` (`components/ui/chart.tsx`) + Recharts 3.8
---
## Error Handling
- **Auth-Race**: `useAuthQuery` verhindert Fehler beim Initialisierungs-Flash
- **Empty States**: Wenn keine Canvases/Transaktionen vorhanden sind, informative Platzhalter zeigen
- **Offline**: Connection-Banner anzeigen, wenn Convex nicht erreichbar
- **Auth-Race:** `useDashboardSnapshot` nutzt `useAuthQuery` intern → skippt bis Auth bereit
- **Empty States:** Wenn keine Canvases/Transaktionen vorhanden sind, informative Platzhalter zeigen
- **Cache-Fehler:** localStorage-Zugriffe sind defensiv gewrappt (quota, disabled storage)
- **Offline:** Snapshot-Cache bietet Fallback bei fehlender Verbindung
---
## Performance
- Canvas-Thumbnails werden über `api.canvases.list` geladen (nur wenn vorhanden)
- Transaktions-Listen sind paginiert oder limitiert (z.B. letzte 20 Transaktionen)
- Kein lokaler State für Canvas-Daten — alles über real-time Convex-Subscriptions
- Gebündelte Snapshot-Query statt 5+ separater Queries → weniger WebSocket-Last
- localStorage-Cache mit 12h TTL → sofortige Anzeige bei wiederholtem Besuch
- Canvas-Thumbnails werden über den Snapshot geladen (nur wenn vorhanden)
- Transaktions-Liste ist auf 20 Einträge limitiert (priorisiert nach Typ)

View File

@@ -62,6 +62,7 @@ transition: all 200ms cubic-bezier(0.16, 1, 0.3, 1); /* expo-out */
| `badge` | `components/ui/badge.tsx` | Status-Tags, Badges |
| `tooltip` | `components/ui/tooltip.tsx` | Explainer-Texte |
| `separator` | `components/ui/separator.tsx` | Trennungen |
| `chart` | `components/ui/chart.tsx` | ShadCN Chart-Wrapper für Recharts (ChartContainer, ChartTooltip, ChartLegend) |
---
@@ -77,3 +78,18 @@ npx shadcn@latest add input
```
Komponenten werden direkt in `components/ui/` abgelegt. Lokale Anpassungen erfolgen durch Überladen von Props oder direkt in der Komponentendatei.
---
## Chart-Komponente (`chart.tsx`)
ShadCN-Wrapper für Recharts v3.8. Bietet konsistentes Styling für Chart-Elemente:
- `ChartContainer` — Root-Wrapper mit CSS-Variablen-basierten Farben
- `ChartTooltip` / `ChartTooltipContent` — Tooltip mit Design-Token-Support
- `ChartLegend` / `ChartLegendContent` — Legende mit konsistentem Styling
- `ChartConfig` — Typ für Chart-Farbkonfiguration
**Verwendung:** Dashboard Credits Activity Chart. Farben über CSS-Variablen (`hsl(var(--primary))` etc.).
**Abhängigkeit:** `recharts` v3.8.0 (in `package.json`).

View File

@@ -27,6 +27,11 @@ Convex ist das vollständige Backend von LemonSpace: Datenbank, Realtime-Subscri
| `storage.ts` | Convex File Storage Helpers + gebündelte Canvas-URL-Auflösung |
| `export.ts` | Canvas-Export-Logik |
| `http.ts` | HTTP-Endpunkte (Webhooks) |
| `dashboard.ts` | Gebündelte Dashboard-Snapshot-Query (Balance, Subscription, Usage, Transactions, Canvases in einem Call) |
| `canvasGraph.ts` | Canvas Graph Query — Performance-optimierte Query für Nodes+Edges in einem Call |
| `ai_errors.ts` | Error-Kategorisierung und User-facing Fehlermeldungen (aus `ai.ts` extrahiert) |
| `ai_node_data.ts` | Node-Data-Helpers (z. B. `getNodeDataRecord`) |
| `ai_retry.ts` | Retry-Logik für AI-Generierung (`generateImageWithAutoRetry`, aus `ai.ts` extrahiert) |
---
@@ -247,6 +252,7 @@ Wirft bei unauthentifiziertem Zugriff. Wird von allen Queries und Mutations genu
- `credits.getSubscription` fällt bei fehlender Session auf Free/Active zurück (statt Throw), damit Tier-UI stabil bleibt.
- `credits.getRecentTransactions` gibt bei fehlender Session `[]` zurück (statt Throw), damit Aktivitätslisten beim Logout sauber leeren.
- `credits.getUsageStats` gibt bei fehlender Session `0`-Statistiken zurück (statt Throw), damit Verbrauchsanzeigen ohne Fehler ausrendern.
- `dashboard.getSnapshot` gibt bei fehlender Session einen vollständigen Default-Snapshot zurück (Balance 0, Free-Tier, leere Listen)
### Idempotente Canvas-Mutations
@@ -305,6 +311,37 @@ Wirft bei unauthentifiziertem Zugriff. Wird von allen Queries und Mutations genu
---
## Dashboard Snapshot (`dashboard.ts`)
Gebündelte Query, die alle Dashboard-relevanten Daten in einem einzigen Convex-Call lädt. Ersetzt separate Queries für Balance, Subscription, UsageStats, Transactions und Canvases.
**`getSnapshot` (query, public):**
- Nutzt `optionalAuth` — gibt bei fehlender Session Default-Werte zurück (kein Throw)
- Lädt parallel: `creditBalances`, `subscriptions`, `creditTransactions` (usage-type), `creditTransactions` (recent, max 80), `canvases`
- Berechnet `monthlyUsage` und `totalGenerations` aus usage-Transactions des aktuellen Monats
- Nutzt `prioritizeRecentCreditTransactions` aus `lib/credits-activity.ts` für sortierte Transaktionsliste
- Gibt strukturiertes Snapshot-Objekt zurück: `{ balance, subscription, usageStats, recentTransactions, canvases, generatedAt }`
---
## Canvas Graph Query (`canvasGraph.ts`)
Performance-optimierte Query, die Nodes und Edges für einen Canvas in einem einzigen Call lädt. Wird vom Frontend über den Canvas Graph Query Cache verwendet.
**`loadCanvasGraph(ctx, { canvasId, userId })` (internal):**
- Prüft Canvas-Existenz und Ownership
- Lädt Nodes und Edges parallel via `Promise.all`
- Performance-Logging bei Queries > 250ms
**`get` (query, public):**
- Nutzt `requireAuth`
- Delegiert an `loadCanvasGraph`
- Loggt bei langsamer Ausführung (`PERFORMANCE_LOG_THRESHOLD_MS = 250`)
**Frontend-Integration:** Der Canvas nutzt diese Query über `canvas-graph-query-cache.ts` (Optimistic Store Helper).
---
## Konventionen
- `internalMutation` / `internalAction` — Nur von anderen Convex-Funktionen aufrufbar, nicht direkt vom Client.

View File

@@ -87,6 +87,30 @@ const { isOnline, pendingOps, syncStatus } = useCanvasSyncStatus()
---
### `use-dashboard-snapshot.ts`
Hook für gebündeltes Dashboard-Datenladen mit localStorage-Cache. Ersetzt separate Queries für Balance, Subscription, UsageStats, Transactions und Canvases.
**Features:**
- Gebündelte Convex-Query (`api.dashboard.getSnapshot`) in einem Call
- Automatisches Caching im localStorage (12h TTL)
- Sofortige Anzeige aus Cache während Live-Query lädt
- Cache-Invalidierung bei Logout (via sessionStorage Tracking)
- Source-Tracking: `{ snapshot, source: "live" | "cache" | "none" }`
**Verwendung:**
```typescript
const { snapshot, source } = useDashboardSnapshot(userId)
// source="cache" → sofortige Anzeige aus Cache
// source="live" → aktuelle Daten vom Server
// source="none" → weder Cache noch Query vorhanden
```
**Typen:**
- `DashboardSnapshot` — Vollständiger Rückgabewert von `api.dashboard.getSnapshot`
---
## Konventionen
- Hooks immer mit `use-` Prefix im Dateinamen

View File

@@ -33,6 +33,8 @@ Geteilte Hilfsfunktionen, Typ-Definitionen und Konfiguration. Keine React-Kompon
| `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 |
| `credits-activity.ts` | Credits-Aktivitäts-Analytics: Transaktions-Priorisierung, Activity-Series, Usage-Domain-Berechnung |
| `dashboard-snapshot-cache.ts` | localStorage-Cache für Dashboard-Snapshots (12h TTL, versioniert) |
---
@@ -198,6 +200,51 @@ Verwendet in `convex/ai.ts` (`pollVideoTask`) und `convex/freepik.ts` (`getVideo
---
## `credits-activity.ts` — Credits Analytics
Analytics-Helpers für die Dashboard Credits-Aktivitäts-Anzeige. Wird von `components/dashboard/credits-activity-chart.tsx` und `convex/dashboard.ts` verwendet.
**Kernfunktionen:**
```typescript
// Transaktions-Priorisierung (Usage → Reservation → Refund → Topup → Subscription)
prioritizeRecentCreditTransactions(transactions, limit)
// Tages-aggregierte Activity-Series für Recharts (max. 7 Tage)
buildCreditsActivitySeries(transactions, availableCredits, locale, maxPoints?)
// Y-Achsen-Domain mit Headroom (mind. 8, +20% Headroom)
calculateUsageActivityDomain(points)
// Credit-Formatierung ("1.234 Cr")
formatCredits(value, locale)
```
**Typen:**
- `CreditTransactionLike` — Minimales Transaction-Interface für Analytics
- `CreditActivityPoint` — Tages-Datenpunkt (`{ day, usage, activity, available }`)
---
## `dashboard-snapshot-cache.ts` — Dashboard Snapshot Cache
localStorage-basierter Cache für Dashboard-Snapshot-Daten.
```typescript
readDashboardSnapshotCache<T>(userId, options?) // Cached Snapshot lesen (mit TTL-Prüfung)
writeDashboardSnapshotCache<T>(userId, snapshot) // Snapshot schreiben
clearDashboardSnapshotCache(userId) // Cache invalidieren
```
**Konfiguration:**
- Namespace: `lemonspace.dashboard`
- Key: `lemonspace.dashboard:snapshot:v1:<userId>`
- TTL: 12 Stunden (`DEFAULT_TTL_MS = 12 * 60 * 60 * 1000`)
- Version: `CACHE_VERSION = 1` — Bumps invalidieren bestehende Caches
- Alle Storage-Zugriffe defensiv (try-catch, quota handling)
---
## `canvas-local-persistence.ts` — localStorage-Cache
```typescript