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

@@ -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`).