diff --git a/apps/cards/COMPETITORS_2026-05.md b/apps/cards/COMPETITORS_2026-05.md
index 168b521e1..377f042ba 100644
--- a/apps/cards/COMPETITORS_2026-05.md
+++ b/apps/cards/COMPETITORS_2026-05.md
@@ -1,4 +1,4 @@
-# Cards — Konkurrenz-Analyse (Mai 2026)
+# Cardecky — Konkurrenz-Analyse (Mai 2026)
> Stand: 2026-05-07. Quellen primär aus offiziellen Pricing-Seiten, G2/Trustpilot/Reddit/HN sowie Wikipedia/Crunchbase. Wo Daten fehlen oder nicht öffentlich sind, ist das explizit vermerkt. Preise schwanken regional/saisonal — die hier genannten Zahlen sind Listenpreise USD, sofern nicht anders angegeben.
@@ -9,7 +9,7 @@
- **Anki bleibt der unschlagbare technische Gold-Standard**, aber UX-Schwächen (FSRS-„Difficulty Hell", Plugin-Hölle, kein natives Cloud-Sync mit Bildern) und der $25 iOS-Preis sind reale Lücken, in die wir stoßen können. Die Übergabe an AnkiHub im Februar 2026 könnte mittelfristig die Open-Source-Dynamik verändern — Beobachten lohnt.
- **Quizlet hat seine eigene Userbase verärgert**: Trustpilot 1.4/5, massive Beschwerden über Paywalls für Funktionen, die früher gratis waren. Genau dieses Vertrauensvakuum füllen Knowt und potenziell wir.
- **AI-Karten-Generierung ist Tischeinsatz, kein Differenzierer mehr.** Quizlet, Quizgecko, Knowt, RemNote, Wisdolia, sogar Memrise haben es. PDF-Import + KI ist erwartete Baseline.
-- **Die „beautiful Anki"-Lücke ist umkämpft**: Mochi (5$/mo), RemNote (8$/mo), Noji (vormals AnkiPro). Cards mit _kostenlosem_ Sync sticht heraus — niemand sonst bietet die Kombination Markdown + FSRS + Cloud-Sync gratis. Das ist unsere wichtigste objektive Differenzierung.
+- **Die „beautiful Anki"-Lücke ist umkämpft**: Mochi (5$/mo), RemNote (8$/mo), Noji (vormals AnkiPro). Cardecky mit _kostenlosem_ Sync sticht heraus — niemand sonst bietet die Kombination Markdown + FSRS + Cloud-Sync gratis. Das ist unsere wichtigste objektive Differenzierung.
- **Brand-Sniping ist real und schädlich**: AnkiPro (jetzt Noji) und AnkiApp (jetzt AlgoApp) haben sich einen Ruf als „Anki-Klone, die täuschen" erarbeitet — inkl. eines 10-tägigen Sync-Outages bei AnkiPro im Mai 2025. Lehre für uns: nie Anki im Namen führen, Kompatibilität sauber kommunizieren.
---
@@ -36,7 +36,7 @@
| **Cerego** | Enterprise B2B Adaptive Learning | proprietär | — | ab $8.33/mo Indiv., Enterprise on req. | Sehr gering (B2B) |
| **NeuraCache** | Notion/Obsidian-Sync für SR | proprietär | Limited | 14d Trial → Pro (Preis nicht klar dokumentiert) | Gering |
-> Threat-Ranking: nur **Anki, Quizlet, Mochi, Knowt** sind Top-Bedrohungen für Cards' Kernzielgruppe. RemNote, Quizgecko, AnkiPro/Noji sind Nebenfront.
+> Threat-Ranking: nur **Anki, Quizlet, Mochi, Knowt** sind Top-Bedrohungen für Cardeckys Kernzielgruppe. RemNote, Quizgecko, AnkiPro/Noji sind Nebenfront.
---
@@ -84,7 +84,7 @@ Quellen: [Quizlet Wikipedia](https://en.wikipedia.org/wiki/Quizlet) · [Trustpil
- **User loben:** Notes + Cards in _einem_ Workflow; flexible nested Outline-Struktur; PDF-Annotation; AI-Generierung aus Notizen/PDFs.
- **User kritisieren:** Steile Lernkurve („nichts versteht man in 10 Min"); UI als überladen empfunden; **Performance-Probleme** (langsam beim Laden großer Datenbanken, iPad-Stabilität); Bugs nach Beta-Updates; non-English-Support schwach.
- **Firma & Geschichte:** Gegründet 2019 von Martin Schneider (MIT) und Moritz Wallawitsch (Berlin, HTW). Sitz: USA. **$2.8M Seed (Sept 2021)** unter General Catalyst. Hat 2025 ~$2M Revenue mit ~18 Personen erreicht.
-- **Bedrohungsgrad: Mittel.** Andere Zielgruppe (PKM-Power-User, Studenten, die Notes wollen). Cards ist fokussierter — wir müssen die „nur-Karten"-Nische gegen ihre Hybrid-Erweiterung verteidigen.
+- **Bedrohungsgrad: Mittel.** Andere Zielgruppe (PKM-Power-User, Studenten, die Notes wollen). Cardecky ist fokussierter — wir müssen die „nur-Karten"-Nische gegen ihre Hybrid-Erweiterung verteidigen.
Quellen: [RemNote Pricing](https://www.remnote.com/pricing) · [Crunchbase RemNote](https://www.crunchbase.com/organization/remnote) · [RemNote Reviews Product Hunt](https://www.producthunt.com/products/remnote/reviews) · [RemNote Performance-Forum](https://forum.remnote.io/t/remnote-is-my-dream-pkm-yet-its-too-slow-am-i-doing-something-wrong/10920) · [Latka RemNote $2M ARR](https://getlatka.com/companies/remnote.com)
@@ -164,7 +164,7 @@ Quellen: [SuperMemo Wikipedia](https://en.wikipedia.org/wiki/SuperMemo) · [Supe
- **User loben:** Schickes Mobile-UI; einfacher Onboarding-Flow; Cross-Device-Sync „out of the box"; community Decks.
- **User kritisieren:** **Brand-Verwirrung** (User dachten, sie laden „echtes" Anki herunter); **10-Tage-Sync-Outage Mai 2025** mit Datenverlust für viele User; Lock-in (Export-Tools wurden vom Anbieter blockiert, ein Migrations-Tool erhielt einen **Rickroll-Response** von AnkiPro); offizielles Anki-Team distanziert sich.
- **Firma & Geschichte:** Anki Pro UAB; Co-Founder **Maksim Abramchuk** (im Crunchbase) und **Andrew Bond** (LinkedIn). 2021 gestartet, 2024/25 Rebrand zu **Noji**. Sitz nicht eindeutig öffentlich (LinkedIn-Indikatoren UK/Osteuropa).
-- **Bedrohungsgrad: Mittel.** Nicht weil sie technisch besser sind, sondern weil Anki-Suchende auf sie reinfallen. **Lehre für Cards: Brand-Hygiene**. Wir sind „Cards" — nie „Anki" im Marketing, klare Trennung kommunizieren, Anki-Import sauber als Bridge dokumentieren.
+- **Bedrohungsgrad: Mittel.** Nicht weil sie technisch besser sind, sondern weil Anki-Suchende auf sie reinfallen. **Lehre für Cardecky: Brand-Hygiene**. Wir sind „Cardecky" — nie „Anki" im Marketing, klare Trennung kommunizieren, Anki-Import sauber als Bridge dokumentieren.
Quellen: [Anki knockoffs (offizielle Anki FAQ)](https://faqs.ankiweb.net/anki-knockoffs.html) · [AnkiPro Ripoff Forum](https://forums.ankiweb.net/t/ankipro-another-ripoff-anki-app/11791) · [Anki Users Get Rickrolled](https://broderic.blog/post/anki-users-get-rickrolled/) · [Noji App Store](https://apps.apple.com/us/app/noji-flashcards-anki-method/id1573585542) · [Crunchbase Anki Pro](https://www.crunchbase.com/organization/anki-pro) · [Speakada: Official Anki vs Fake Apps](https://speakada.com/official-anki-vs-fake-apps-the-critical-mistake-costing-language-learners-hours/)
@@ -298,7 +298,7 @@ Quellen: [NeuraCache](https://neuracache.com/) · [NeuraCache App Store](https:/
---
-## 4. Schluss-Empfehlung: 3 Differenzierungs-Hebel für Cards
+## 4. Schluss-Empfehlung: 3 Differenzierungs-Hebel für Cardecky
### Hebel 1: **„Free Sync" konsequent ausspielen**
@@ -318,13 +318,13 @@ Anki bleibt Power-User-Standard, aber Anki-User klagen über UX, FSRS-Tweaking u
**Action:**
-- Eine dezidierte Landingpage `cards.mana.how/from-anki` mit ehrlichem Vergleich (was wir besser machen, was Anki noch besser kann), Migrationsanleitung, und expliziter Distanzierung von AnkiPro/AnkiApp/Noji.
-- Eine ehrliche Story dazu („Wir sind nicht Anki. Wir sind Cards. Aber wir respektieren deine Anki-Karten."). Das positioniert uns als seriöse Alternative gegen die Brand-Sniper.
+- Eine dezidierte Landingpage `cardecky.com/from-anki` mit ehrlichem Vergleich (was wir besser machen, was Anki noch besser kann), Migrationsanleitung, und expliziter Distanzierung von AnkiPro/AnkiApp/Noji.
+- Eine ehrliche Story dazu („Wir sind nicht Anki. Wir sind Cardecky. Aber wir respektieren deine Anki-Karten."). Das positioniert uns als seriöse Alternative gegen die Brand-Sniper.
- Für Bonus-Punkte: Imports von Mochi-Decks und Quizlet-Sets ebenfalls anbieten — Knowt lebt davon, wir können das auch.
### Hebel 3: **„Local-First PWA" als Tech-Identität, nicht nur Implementierungsdetail**
-Cards' Local-First + PWA-Architektur ist konzeptionell anders als Quizlet/Knowt (Web-First) und besser als Mochi auf iOS (App-Store-Friktion). Wir sind installierbar, offline-funktional, ohne App Store. Das schlägt mehrere Fliegen:
+Cardeckys Local-First + PWA-Architektur ist konzeptionell anders als Quizlet/Knowt (Web-First) und besser als Mochi auf iOS (App-Store-Friktion). Wir sind installierbar, offline-funktional, ohne App Store. Das schlägt mehrere Fliegen:
- Kein iOS-30%-Tax (vs AnkiMobile-Modell, das deshalb $25 kostet)
- Kein Vendor-Lock-in (Daten bleiben im Browser/lokal nutzbar)
@@ -337,7 +337,7 @@ Cards' Local-First + PWA-Architektur ist konzeptionell anders als Quizlet/Knowt
## Bonus: Was wir _nicht_ tun sollten
-- **Nicht „Anki" im Namen führen** — siehe AnkiPro/AnkiApp Reputation. „Cards" ist neutral und reicht.
+- **Nicht „Anki" im Namen führen** — siehe AnkiPro/AnkiApp Reputation. „Cardecky" ist neutral, freundlich, und distanziert sich klar.
- **Nicht die SR-Algorithmus-Race spielen** — FSRS v6 reicht. SuperMemo SM-20 ist kein Marketing-Argument für 99% der User.
- **Nicht in Sprach-Lernen pivotieren** — Memrise und Duolingo besitzen das Feld, andere Mechaniken nötig.
- **Nicht alle AI-Features paywallen** — Knowt zeigt: ein großzügiges Free-Tier mit AI ist der Hebel gegen Quizlet.
diff --git a/apps/cards/GUIDELINES.md b/apps/cards/GUIDELINES.md
index 54174a21c..38f8096b0 100644
--- a/apps/cards/GUIDELINES.md
+++ b/apps/cards/GUIDELINES.md
@@ -1,12 +1,13 @@
-# Cards — Projekt-Leitlinien
+# Cardecky — Projekt-Leitlinien
Verbindliche Regeln für den Spinoff. Ziel: in wenigen Wochen ein
ausspielbares Web-MVP, das ausschließlich seinen *Core Gameloop*
beherrscht und alles andere von zentralen Mana-Bausteinen erbt.
**Status:** Planungsphase, noch kein Code.
-**Name:** Cards.
-**Domain:** `cards.mana.how` (Subdomain unter `*.mana.how`, SSO über mana-auth).
+**Name:** Cardecky.
+**App-Domain:** `cardecky.mana.how` (Subdomain unter `*.mana.how`, SSO über mana-auth).
+**Marketing-Landing:** `cardecky.com` (eigene Domain, statisch, SEO/Akquise — keine Auth, leitet auf `cardecky.mana.how` für die App).
**Zugang:** offen für jeden eingeloggten Mana-User (`requiredTier: 'public'`, kein Beta-Gate).
## 1. Mission in einem Satz
@@ -20,7 +21,7 @@ Wie bei einem Spielprototyp gilt: alles, was nicht zum Loop gehört,
wird zurückgestellt. Erst wenn der Loop sich gut anfühlt und Nutzer ihn
freiwillig wiederholen, wird gebaut, was drumherum gehört.
-### Der Core Gameloop von Cards
+### Der Core Gameloop von Cardecky
```
Start
@@ -86,7 +87,7 @@ Jede dieser Features ist legitim — aber nur, wenn der Loop steht.
3. **Bevorzugt was im Verein schon läuft.** Neue Technologie nur einführen, wenn ein konkreter Engpass es verlangt und kein vorhandenes Tool es löst.
4. **Zentrale Mana-Dienste statt Eigenbau.** Auth, Sync, Analytics, Notifications, Media usw. werden NICHT neu gebaut — siehe §5.
5. **Local-First wie der Rest des Verein-Stacks.** IndexedDB als Quelle der Wahrheit, Sync nach Postgres im Hintergrund.
-6. **`cards.mana.how` als Subdomain unter `*.mana.how`.** Kein eigenes Auth-System, kein eigenes Hosting-Setup — Eintrag in `PRODUCTION_TRUSTED_ORIGINS` + Cloudflare-Tunnel-Route reichen.
+6. **`cardecky.mana.how` als Subdomain unter `*.mana.how`.** Kein eigenes Auth-System, kein eigenes Hosting-Setup — Eintrag in `PRODUCTION_TRUSTED_ORIGINS` + Cloudflare-Tunnel-Route reichen.
7. **Eine UI-Schicht, ein Theme.** Wir verwenden `@mana/shared-theme(-ui)` und `@mana/shared-ui` so weit es geht — kein paralleles Design-System.
8. **Erweiterbare Daten, simples UI.** Das Datenmodell denkt zukünftige Kartentypen mit (siehe §6), das UI zeigt in Phase 1 nur die vier definierten Typen.
@@ -150,9 +151,9 @@ Verein-Services).
## 5. Zentrale Mana-Bausteine (Pflicht in Phase 1)
### Services (laufen bereits, nur konsumieren)
-| Service | Port | Wofür in Cards |
+| Service | Port | Wofür in Cardecky |
|---|---|---|
-| `mana-auth` | 3001 | SSO, JWT, Sessions, Tier-Claims. Cards-Origin in `PRODUCTION_TRUSTED_ORIGINS` eintragen. |
+| `mana-auth` | 3001 | SSO, JWT, Sessions, Tier-Claims. Cardecky-Origin in `PRODUCTION_TRUSTED_ORIGINS` eintragen. |
| `mana-sync` | 3050 | Sync der `cards`-AppId-Daten (Decks, Karten, Reviews, StudyBlocks). |
| `mana-user` | 3062 | Profilinfos / Settings. |
| `mana-analytics` | 3064 | Page-Views, Loop-Events (siehe §11). |
@@ -164,7 +165,7 @@ Verein-Services).
| `mana-media` | 3015 | **Erst wenn Bilder in Karten erlaubt sind.** |
### Workspace-Pakete (`@mana/*`)
-| Paket | Wofür in Cards |
+| Paket | Wofür in Cardecky |
|---|---|
| `@mana/shared-auth` | Client-seitiger Auth-Hook (SSO-Flow, JWT-Handling). |
| `@mana/shared-auth-ui` | Login/Logout-Komponenten. |
@@ -198,7 +199,7 @@ Verein-Services).
### Datenpfad
-Cards übernimmt 1:1 das Mana-Datenpfad-Pattern:
+Cardecky übernimmt 1:1 das Mana-Datenpfad-Pattern:
```
User-Aktion → Store → encryptRecord → Dexie → Hooks (_pendingChanges)
@@ -267,12 +268,12 @@ beim ersten Schema-Upgrade auf `type='basic'` mit
## 7. Daten-Contract mit dem mana-Modul
Wichtig: das **bestehende `cards`-Modul in der Mana-Web-App bleibt
-erhalten**. Cards-Standalone und mana-Modul schreiben in dieselben
+erhalten**. Cardecky und das mana-Modul schreiben in dieselben
Postgres-Tabellen.
Daher gilt:
- Schema-Änderungen werden **gemeinsam** im mana-Modul und im
- Cards-Standalone-Code rolled out (nie nur auf einer Seite).
+ Cardecky-Code rolled out (nie nur auf einer Seite).
- Encryption-Registry-Einträge müssen in beiden Frontends identisch
sein (Field-Allowlist).
- Migrationen über `docs/DATABASE_MIGRATIONS.md`.
@@ -285,7 +286,7 @@ gibt es nie zwei Wahrheiten zur Datenstruktur.
Phase 1 ist fertig, wenn:
-1. Ein eingeloggter Mana-User kann auf `cards.mana.how`
+1. Ein eingeloggter Mana-User kann auf `cardecky.mana.how`
- mindestens ein Deck anlegen,
- Karten manuell hinzufügen (Basic, Basic+Reverse, Cloze, Type-In),
- Markdown im Front/Back nutzen (Bold, Listen, Code, Links),
@@ -300,7 +301,7 @@ Phase 1 ist fertig, wenn:
- „Login → Deck anlegen → Basic-Karte → Lernsession → bewerten"
- „Cloze-Karte mit zwei Clustern → erzeugt zwei Subkarten"
- „Type-In: korrekte Antwort = grün, falsche = rot"
-8. Container baut & läuft auf dem Mac mini hinter Cloudflare Tunnel (`cards.mana.how`).
+8. Container baut & läuft auf dem Mac mini hinter Cloudflare Tunnel (`cardecky.mana.how`).
Alles andere ist Phase 2.
@@ -360,7 +361,7 @@ konkrete Frage entsteht, die Daten beantworten sollen.
## 12. Hinweis im mana-Modul
-Sobald `cards.mana.how` live ist, bekommt das mana-Modul einen
+Sobald `cardecky.mana.how` live ist, bekommt das mana-Modul einen
**dezenten** Hinweis (z.B. ein Banner oder Badge über der ListView):
-"Cards gibt es jetzt auch als eigenständige App". Kein Pop-up, kein
+"Cardecky gibt es jetzt auch als eigenständige App". Kein Pop-up, kein
forcierter Redirect — User entscheiden selbst.
diff --git a/apps/cards/README.md b/apps/cards/README.md
index 3cb92ba7a..75891b4a9 100644
--- a/apps/cards/README.md
+++ b/apps/cards/README.md
@@ -1,6 +1,6 @@
-# Cards
+# Cardecky
-Spaced-repetition flashcards on **cards.mana.how**.
+Spaced-repetition flashcards on **cardecky.mana.how**.
Phase-1 standalone web app. The frontend lives here; data, auth, and
sync are shared with the rest of the Mana stack:
@@ -9,7 +9,7 @@ sync are shared with the rest of the Mana stack:
- **Sync:** mana-sync, app-id `cards`
- **Storage:** `mana_platform.cards.*` (Postgres, RLS)
-The same `cards` data backs the **mana** built-in Cards module at
+The same `cards` data backs the **mana** built-in Cardecky module at
`mana.how/cards`. Schema changes ship to both frontends together — see
`apps/cards/GUIDELINES.md`.
@@ -30,5 +30,5 @@ will land in Phase 2/3.
```bash
pnpm install
-pnpm --filter @cards/web dev # cards.mana.how on http://localhost:5180
+pnpm --filter @cards/web dev # cardecky.mana.how on http://localhost:5180
```
diff --git a/apps/cards/apps/web/Dockerfile b/apps/cards/apps/web/Dockerfile
index ca407e02e..4f4174f67 100644
--- a/apps/cards/apps/web/Dockerfile
+++ b/apps/cards/apps/web/Dockerfile
@@ -1,5 +1,5 @@
# syntax=docker/dockerfile:1
-# Cards Standalone — cards.mana.how. Mirrors apps/manavoxel/apps/web/Dockerfile.
+# Cardecky Standalone — cardecky.mana.how. Mirrors apps/manavoxel/apps/web/Dockerfile.
# ─── Stage 1: Build ──────────────────────────────────────────
FROM sveltekit-base:local AS builder
diff --git a/apps/cards/apps/web/src/hooks.server.ts b/apps/cards/apps/web/src/hooks.server.ts
index 1bef6e03a..846ce98e2 100644
--- a/apps/cards/apps/web/src/hooks.server.ts
+++ b/apps/cards/apps/web/src/hooks.server.ts
@@ -5,7 +5,7 @@ import type { Handle } from '@sveltejs/kit';
*
* `@mana/shared-auth-ui` reads `window.__PUBLIC_MANA_AUTH_URL__` to know
* where to POST /api/v1/auth/login (and friends). Without this hook the
- * client falls back to a relative URL → 404 on cards.mana.how.
+ * client falls back to a relative URL → 404 on cardecky.mana.how.
*
* `process.env.PUBLIC_MANA_*_URL_CLIENT` come from the container
* environment (docker-compose.macmini.yml). $env/static/public would
diff --git a/apps/cards/apps/web/src/lib/api/cards-api.ts b/apps/cards/apps/web/src/lib/api/cards-api.ts
index c00a40489..6ed102197 100644
--- a/apps/cards/apps/web/src/lib/api/cards-api.ts
+++ b/apps/cards/apps/web/src/lib/api/cards-api.ts
@@ -1,5 +1,5 @@
/**
- * Thin client for cards-server (https://cards-api.mana.how / dev :3072).
+ * Thin client for cards-server (https://cardecky-api.mana.how / dev :3072).
*
* The auth-store provides the JWT; we never read tokens from storage
* here directly so there's only one place that knows about token
diff --git a/apps/cards/apps/web/src/lib/components/PublishDeckModal.svelte b/apps/cards/apps/web/src/lib/components/PublishDeckModal.svelte
index 3348ed210..97145ffed 100644
--- a/apps/cards/apps/web/src/lib/components/PublishDeckModal.svelte
+++ b/apps/cards/apps/web/src/lib/components/PublishDeckModal.svelte
@@ -48,7 +48,7 @@
// svelte-ignore state_referenced_locally
let deckDescription = $state(deck.description ?? '');
let deckLanguage = $state('de');
- let deckLicense = $state<'CC0-1.0' | 'CC-BY-4.0' | 'CC-BY-SA-4.0' | 'Cards-Personal-Use-1.0'>(
+ let deckLicense = $state<'CC0-1.0' | 'CC-BY-4.0' | 'CC-BY-SA-4.0' | 'Cardecky-Personal-Use-1.0'>(
'CC-BY-4.0'
);
let deckSemver = $state('1.0.0');
@@ -153,7 +153,7 @@
Erstelle ein Author-Profil — andere User finden deine Decks unter
- cards.mana.how/u/dein-slug.
+ cardecky.mana.how/u/dein-slug.
@@ -206,7 +206,7 @@
Veröffentlicht als cards.mana.how/d/{deckSlug || '...'}cardecky.mana.how/d/{deckSlug || '...'}
@@ -263,7 +263,7 @@
CC-BY 4.0 — frei mit Namensnennung
CC-BY-SA 4.0 — share-alike
CC0 — gemeinfrei
- Personal Use — nur lernen
+ Personal Use — nur lernen
diff --git a/apps/cards/apps/web/src/lib/stores/auth.svelte.ts b/apps/cards/apps/web/src/lib/stores/auth.svelte.ts
index e5ba92be9..ce4e9f88c 100644
--- a/apps/cards/apps/web/src/lib/stores/auth.svelte.ts
+++ b/apps/cards/apps/web/src/lib/stores/auth.svelte.ts
@@ -2,7 +2,7 @@
* Auth Store — uses the shared Mana auth factory.
*
* SSO: tokens land in the shared `*.mana.how` storage so a user already
- * signed into mana.how / cards.mana.how lands directly in the app
+ * signed into mana.how / cardecky.mana.how lands directly in the app
* without re-typing credentials. The factory wires up the token
* manager + refresh + storage adapter for us.
*/
diff --git a/apps/cards/docs/MARKETPLACE_PLAN.md b/apps/cards/docs/MARKETPLACE_PLAN.md
index afe314cd8..d753bc7a4 100644
--- a/apps/cards/docs/MARKETPLACE_PLAN.md
+++ b/apps/cards/docs/MARKETPLACE_PLAN.md
@@ -1,4 +1,4 @@
-# Cards-Marktplatz — Plan
+# Cardecky-Marktplatz — Plan
> **Status**: Plan, kein Code. Stand 2026-05-07.
> **Goal-Setting**: Vollvision, kein MVP-Druck. Wir bauen die optimale Lösung.
@@ -38,7 +38,7 @@
2. **Public-Decks leben separat** vom Local-First-Sync-Pfad (eigene Postgres-Tabellen, eigene Service, eigene RLS-Policies). Kein Vermischen mit `mana_sync.sync_changes`.
3. **Subscribed Decks sind unidirektional**: Author → Subscribers. Updates fließen einseitig. Wer ändern will, forkt.
4. **Content-Hash überall.** Jede Karte und jede Version bekommt einen deterministischen SHA-256 → Trust + Cache + Diff kostenlos.
-5. **Lizenzen sind explizit + maschinen-lesbar** (SPDX-IDs: `CC0-1.0`, `CC-BY-4.0`, `CC-BY-SA-4.0`, plus eigener `Cards-Personal-Use-1.0` für Default-Käufe und `Cards-Pro-Only-1.0` für paid Decks).
+5. **Lizenzen sind explizit + maschinen-lesbar** (SPDX-IDs: `CC0-1.0`, `CC-BY-4.0`, `CC-BY-SA-4.0`, plus eigener `Cardecky-Personal-Use-1.0` für Default-Käufe und `Cardecky-Pro-Only-1.0` für paid Decks).
6. **AI ist Moderator, nicht Gatekeeper** — KI-First-Pass + Human-Review-Eskalation. Niemals KI-allein-Take-down.
7. **Search ist von der DB entkoppelt** — Read-Only-Index, asynchron befüllt. Bricht der Search-Service, läuft der Marktplatz weiter.
8. **mana-credits ist die einzige Geld-Schnittstelle** — niemals Stripe direkt im cards-server. Alles geht über `/api/v1/credits/use`, `/credits/grant`, `/credits/reservations/*`.
@@ -93,7 +93,7 @@ public_decks (
takedown_at timestamptz,
takedown_reason text,
created_at timestamptz DEFAULT now(),
- CONSTRAINT price_requires_license CHECK (price_credits = 0 OR license = 'Cards-Pro-Only-1.0')
+ CONSTRAINT price_requires_license CHECK (price_credits = 0 OR license = 'Cardecky-Pro-Only-1.0')
)
public_deck_versions (
@@ -418,7 +418,7 @@ POST /v1/notifications/:id/read — Mark read
PATCH /v1/notifications/preferences — Settings (welche Events triggern Push)
```
-## 8. UI / Routes (Cards-Frontend)
+## 8. UI / Routes (Cardecky-Frontend)
```
/explore — Featured + Trending + Tag-Tree + Search-Bar
@@ -485,7 +485,7 @@ Marktplatz ohne Decks ist nutzlos. Drei parallele Hebel:
- Integration-Tests (Drizzle + Vitest)
- mana-auth-JWT-Middleware (`@mana/shared-hono`)
- Container in `docker-compose.macmini.yml`
-- Cloudflare-Tunnel-Route `cards-api.mana.how` → `:3072`
+- Cloudflare-Tunnel-Route `cardecky-api.mana.how` → `:3072`
### Phase β — Author-Workflow ✅ shipped
@@ -510,7 +510,7 @@ Marktplatz ohne Decks ist nutzlos. Drei parallele Hebel:
### Phase δ — Subscribe + Updates + Smart-Merge ✅ shipped
-- ✅ „Abonnieren"-Button → lädt aktuelle Version in lokale Cards-DB
+- ✅ „Abonnieren"-Button → lädt aktuelle Version in lokale Cardecky-DB
- 🟡 Update-Detection: Polling beim Öffnen der Deck-Page; **kein** WebSocket-Push (kommt in θ/ι)
- ✅ **Smart-Merge**: Diff zwischen Versionen → unveränderte Karten behalten FSRS-State; geänderte erben FSRS-State über Ord-Pairing-Heuristik; neue + entfernte werden korrekt behandelt
- ✅ Diff-View „+N · ~N · −N" mit Apply-Button auf der Deck-Page
@@ -565,7 +565,7 @@ Marktplatz ohne Decks ist nutzlos. Drei parallele Hebel:
### Phasen die später kommen (explizit nicht in diesem Plan)
- **Phase λ — Co-Learn-Sessions**: WebSocket-Multiplayer, gemeinsam lernen, Sehen-was-andere-machen
-- **Phase μ — Mobile-Apps**: Expo-App (Cards-Standalone-Mobile)
+- **Phase μ — Mobile-Apps**: Expo-App (Cardecky-Standalone-Mobile)
- **Phase ν — Author-Tools**: Bulk-Edit-UI für Authoren mit großen Decks, Style-Templates, Author-Analytics-Deep-Dive
- **Phase ξ — Lern-Battles**: Asynchroner Wettkampf-Modus
@@ -629,7 +629,7 @@ Marktplatz ohne Decks ist nutzlos. Drei parallele Hebel:
| Phase | Status | Was läuft | Was fehlt |
|-------|--------|-----------|-----------|
-| α — Skelett | ✅ | cards-server lebt auf 3072, Schema gepushed, JWT-Auth, Container in `docker-compose.macmini.yml`, Tunnel-Route `cards-api.mana.how` | — |
+| α — Skelett | ✅ | cards-server lebt auf 3072, Schema gepushed, JWT-Auth, Container in `docker-compose.macmini.yml`, Tunnel-Route `cardecky-api.mana.how` | — |
| β — Author-Workflow | ✅ | Profil-Claim, Publish, Lizenz, Preis, AI-Mod-Verdict | Tag-Picker im Publish, Author-Dashboard-Stats |
| γ — Discovery | ✅ | `/explore`, Stars, Follows, Author-Profile, Trending | tsvector-FTS, Tag-Tree, Activity-Feed |
| δ — Subscribe + Smart-Merge | ✅ | Pull, Smart-Merge mit FSRS-State-Erhalt, Diff-View | WebSocket-Push, Update-Mails |
@@ -640,7 +640,7 @@ Marktplatz ohne Decks ist nutzlos. Drei parallele Hebel:
| ι — Optimierung | ⏳ | — | Search-Service, CDN, Rate-Limiting, Materialized Views |
| λ / μ / ν / ξ | ⏳ | — | später (Co-Learn, Mobile, Author-Tools, Lern-Battles) |
-**Live-Domains**: `cards.mana.how` (Web) · `cards-api.mana.how` (API).
+**Live-Domains**: `cardecky.mana.how` (Web) · `cardecky-api.mana.how` (API).
**Nächste sinnvolle Schritte (Empfehlung)**:
diff --git a/apps/cards/package.json b/apps/cards/package.json
index be4804ca0..b5f750efe 100644
--- a/apps/cards/package.json
+++ b/apps/cards/package.json
@@ -2,7 +2,7 @@
"name": "cards",
"version": "0.1.0",
"private": true,
- "description": "Cards — Spaced-Repetition flashcards on cards.mana.how. Standalone Phase-1 frontend; data shared with the mana cards module via mana-sync.",
+ "description": "Cardecky — Spaced-Repetition flashcards on cardecky.mana.how (Marketing-Landing: cardecky.com). Standalone Phase-1 frontend; data shared with the mana cards module via mana-sync.",
"scripts": {
"dev": "pnpm run --filter=@cards/* --parallel dev"
}
diff --git a/apps/docs/src/content/docs/deployment/cloudflare-pages.mdx b/apps/docs/src/content/docs/deployment/cloudflare-pages.mdx
index ee8ebf5c9..675ab3772 100644
--- a/apps/docs/src/content/docs/deployment/cloudflare-pages.mdx
+++ b/apps/docs/src/content/docs/deployment/cloudflare-pages.mdx
@@ -16,7 +16,7 @@ All landing pages and static sites are deployed to **Cloudflare Pages** using Di
| Chat | `@chat/landing` | `chat-landing` | chat.mana.how |
| Picture | `@picture/landing` | `picture-landing` | picture.mana.how |
| Mana | `@mana/landing` | `mana-landing` | mana.how |
-| Cards | `@cards/landing` | `cards-landing` | cards.mana.how |
+| Cardecky | `@cards/landing` | `cardecky-landing` | cardecky.com |
| Quotes | `@quotes/landing` | `quotes-landing` | quotes.mana.how |
| Docs | `@mana/docs` | `mana-docs` | docs.mana.how |
diff --git a/apps/mana/apps/landing/src/components/navigation/Footer.astro b/apps/mana/apps/landing/src/components/navigation/Footer.astro
index 9f55f43ff..42eb969fe 100644
--- a/apps/mana/apps/landing/src/components/navigation/Footer.astro
+++ b/apps/mana/apps/landing/src/components/navigation/Footer.astro
@@ -19,7 +19,7 @@ const ecosystemApps = [
{ label: 'Picture', href: 'https://picture.mana.how', status: 'alpha' },
{ label: 'Storage', href: 'https://storage.mana.how', status: 'alpha' },
{ label: 'Presi', href: 'https://presi.mana.how', status: 'alpha' },
- { label: 'Cards', href: 'https://cards.mana.how', status: 'alpha' },
+ { label: 'Cardecky', href: 'https://cardecky.mana.how', status: 'alpha' },
{ label: 'Mukke', href: 'https://mukke.mana.how', status: 'alpha' },
{ label: 'Photos', href: 'https://photos.mana.how', status: 'alpha' },
{ label: 'SkillTree', href: 'https://skilltree.mana.how', status: 'alpha' },
diff --git a/apps/mana/apps/landing/src/pages/apps/index.astro b/apps/mana/apps/landing/src/pages/apps/index.astro
index d6ee747bb..f36c431fd 100644
--- a/apps/mana/apps/landing/src/pages/apps/index.astro
+++ b/apps/mana/apps/landing/src/pages/apps/index.astro
@@ -40,7 +40,7 @@ const sections: Section[] = [
{
label: 'Lernen',
apps: [
- { name: 'Cards', icon: 'ph:cards-bold', tagline: 'KI Karteikarten', url: 'https://cards.mana.how' },
+ { name: 'Cardecky', icon: 'ph:cards-bold', tagline: 'KI Karteikarten', url: 'https://cardecky.mana.how' },
{ name: 'SkilltTree', icon: 'ph:tree-structure-bold', tagline: 'Skill-Tracking', url: 'https://skilltree.mana.how' },
{ name: 'Quotes', icon: 'ph:quotes-bold', tagline: 'Zitate & Inspiration', url: 'https://quotes.mana.how' },
],
diff --git a/apps/mana/apps/web/src/lib/config/apps.ts b/apps/mana/apps/web/src/lib/config/apps.ts
index 660606217..a227447ff 100644
--- a/apps/mana/apps/web/src/lib/config/apps.ts
+++ b/apps/mana/apps/web/src/lib/config/apps.ts
@@ -261,7 +261,7 @@ export const appConfigs: Record = {
},
],
dashboardRoute: '/',
- website: 'https://cards.mana.how',
+ website: 'https://cardecky.mana.how',
},
todo: {
diff --git a/apps/mana/apps/web/src/lib/modules/cards/ListView.svelte b/apps/mana/apps/web/src/lib/modules/cards/ListView.svelte
index 09de2b16d..a866faf59 100644
--- a/apps/mana/apps/web/src/lib/modules/cards/ListView.svelte
+++ b/apps/mana/apps/web/src/lib/modules/cards/ListView.svelte
@@ -1,6 +1,8 @@
+{#if !standaloneHintDismissed}
+
+
+ Cardecky gibt es jetzt auch als eigenständige App auf
+ cardecky.mana.how — gleiche Daten, fokussierte UI.
+
+
×
+
+{/if}
d.id} emptyTitle="Keine Decks">
{#snippet header()}
{decks.length} Decks
diff --git a/apps/mana/apps/web/src/lib/modules/cards/types.ts b/apps/mana/apps/web/src/lib/modules/cards/types.ts
index d450b73c1..263c01487 100644
--- a/apps/mana/apps/web/src/lib/modules/cards/types.ts
+++ b/apps/mana/apps/web/src/lib/modules/cards/types.ts
@@ -1,6 +1,6 @@
/**
- * Cards module — types are now sourced from `@mana/cards-core` so the
- * standalone cards.mana.how app and this in-mana module stay in sync.
+ * Cardecky / cards module — types are now sourced from `@mana/cards-core`
+ * so the standalone cardecky.mana.how app and this in-mana module stay in sync.
*
* This file is a thin re-export to keep existing
* `from './types'` / `from '$lib/modules/cards/types'` imports working.
diff --git a/cloudflared-config.yml b/cloudflared-config.yml
index ef5a4d86a..e16843a46 100644
--- a/cloudflared-config.yml
+++ b/cloudflared-config.yml
@@ -56,10 +56,10 @@ ingress:
service: http://localhost:5000
- hostname: plants.mana.how
service: http://localhost:5000
- # cards.mana.how → standalone Cards SvelteKit container (apps/cards/apps/web).
+ # cardecky.mana.how → standalone Cardecky SvelteKit container (apps/cards/apps/web).
# Was pointed at :5000 (the unified mana-web) until the standalone spinoff
# landed. mana.how/cards still serves the in-mana cards module.
- - hostname: cards.mana.how
+ - hostname: cardecky.mana.how
service: http://localhost:5180
- hostname: storage.mana.how
service: http://localhost:5000
@@ -148,7 +148,7 @@ ingress:
service: http://localhost:3063
- hostname: events.mana.how
service: http://localhost:3065
- - hostname: cards-api.mana.how
+ - hostname: cardecky-api.mana.how
service: http://localhost:3072
- hostname: feedback.mana.how
service: http://localhost:3064
@@ -203,6 +203,15 @@ ingress:
# ============================================
# Self-hosted landing pages (Nginx on port 4400)
# ============================================
+ # Cardecky-Migration: alte Hostnames → Nginx 301-Redirect (2026-05-08).
+ - hostname: cards.mana.how
+ service: http://localhost:4400
+ - hostname: cards-api.mana.how
+ service: http://localhost:4400
+ # cardecky.com Marketing-Landing — DNS zeigt am Cloudflare-Zone von
+ # cardecky.com auf diesen Tunnel; nginx-Block in docker/nginx/landings.conf.
+ - hostname: cardecky.com
+ service: http://localhost:4400
- hostname: it.mana.how
service: http://localhost:4400
- hostname: chats.mana.how
diff --git a/docker-compose.macmini.yml b/docker-compose.macmini.yml
index 49164f7d6..f5b83e469 100644
--- a/docker-compose.macmini.yml
+++ b/docker-compose.macmini.yml
@@ -250,7 +250,7 @@ services:
# Enforced by services/mana-auth/src/auth/sso-config.spec.ts.
# All productivity modules now live under mana.how (path-based) —
# no per-module subdomain entries required here.
- CORS_ORIGINS: https://mana.how,https://auth.mana.how,https://whopxl.mana.how,https://cards.mana.how,https://cards-api.mana.how,https://memoro-app.mana.how
+ CORS_ORIGINS: https://mana.how,https://auth.mana.how,https://whopxl.mana.how,https://cardecky.mana.how,https://cardecky-api.mana.how,https://memoro-app.mana.how
ports:
- "3001:3001"
healthcheck:
@@ -288,7 +288,7 @@ services:
STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY:-}
STRIPE_WEBHOOK_SECRET: ${STRIPE_CREDITS_WEBHOOK_SECRET:-}
BASE_URL: https://credits.mana.how
- CORS_ORIGINS: https://mana.how,https://chat.mana.how,https://picture.mana.how,https://todo.mana.how,https://quotes.mana.how,https://calendar.mana.how,https://clock.mana.how,https://contacts.mana.how,https://cards.mana.how,https://presi.mana.how,https://storage.mana.how,https://food.mana.how,https://plants.mana.how,https://music.mana.how,https://context.mana.how,https://photos.mana.how,https://questions.mana.how,https://calc.mana.how
+ CORS_ORIGINS: https://mana.how,https://chat.mana.how,https://picture.mana.how,https://todo.mana.how,https://quotes.mana.how,https://calendar.mana.how,https://clock.mana.how,https://contacts.mana.how,https://cardecky.mana.how,https://presi.mana.how,https://storage.mana.how,https://food.mana.how,https://plants.mana.how,https://music.mana.how,https://context.mana.how,https://photos.mana.how,https://questions.mana.how,https://calc.mana.how
ports:
- "3002:3002"
healthcheck:
@@ -304,7 +304,7 @@ services:
- "traefik.http.services.mana-credits.loadbalancer.server.port=3002"
cards-server:
- # Cards-Marketplace + Community backend. See
+ # Cardecky Marketplace + Community backend. See
# apps/cards/docs/MARKETPLACE_PLAN.md for the full design.
build:
context: .
@@ -328,7 +328,7 @@ services:
MANA_MEDIA_URL: http://mana-media:3015
MANA_NOTIFY_URL: http://mana-notify:3040
MANA_SERVICE_KEY: ${MANA_SERVICE_KEY}
- CORS_ORIGINS: https://cards.mana.how,https://mana.how
+ CORS_ORIGINS: https://cardecky.mana.how,https://mana.how
AUTHOR_PAYOUT_STANDARD_BPS: 8000
AUTHOR_PAYOUT_VERIFIED_BPS: 9000
COMMUNITY_VERIFY_STARS: 500
@@ -439,7 +439,7 @@ services:
DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD:-mana123}@postgres:5432/mana_platform
MANA_AUTH_URL: http://mana-auth:3001
MANA_SERVICE_KEY: ${MANA_SERVICE_KEY}
- CORS_ORIGINS: https://mana.how,https://calc.mana.how,https://calendar.mana.how,https://chat.mana.how,https://clock.mana.how,https://contacts.mana.how,https://context.mana.how,https://cards.mana.how,https://music.mana.how,https://food.mana.how,https://photos.mana.how,https://picture.mana.how,https://plants.mana.how,https://presi.mana.how,https://questions.mana.how,https://storage.mana.how,https://todo.mana.how,https://quotes.mana.how
+ CORS_ORIGINS: https://mana.how,https://calc.mana.how,https://calendar.mana.how,https://chat.mana.how,https://clock.mana.how,https://contacts.mana.how,https://context.mana.how,https://cardecky.mana.how,https://music.mana.how,https://food.mana.how,https://photos.mana.how,https://picture.mana.how,https://plants.mana.how,https://presi.mana.how,https://questions.mana.how,https://storage.mana.how,https://todo.mana.how,https://quotes.mana.how
ports:
- "3062:3062"
healthcheck:
@@ -621,7 +621,7 @@ services:
CORS_ORIGINS: "https://mana.how,https://*.mana.how"
MANA_CREDITS_URL: http://mana-credits:3002
MANA_SERVICE_KEY: ${MANA_SERVICE_KEY}
- # Apps that bypass the sync-subscription billing gate. Cards
+ # Apps that bypass the sync-subscription billing gate. Cardecky
# promises free Sync per its Phase-1 GUIDELINES.
BILLING_EXEMPT_APPS: cards
ports:
@@ -954,7 +954,7 @@ services:
start_period: 45s
cards-web:
- # Standalone Cards frontend on cards.mana.how — separate SvelteKit
+ # Standalone Cardecky frontend on cardecky.mana.how — separate SvelteKit
# container that consumes the same mana-sync 'cards' app-id as the
# in-mana cards module. See apps/cards/GUIDELINES.md.
build:
@@ -976,7 +976,7 @@ services:
PUBLIC_MANA_SYNC_URL_CLIENT: https://sync.mana.how
PUBLIC_MANA_LLM_URL_CLIENT: https://llm.mana.how
PUBLIC_MANA_MEDIA_URL_CLIENT: https://media.mana.how
- PUBLIC_CARDS_API_URL_CLIENT: https://cards-api.mana.how
+ PUBLIC_CARDS_API_URL_CLIENT: https://cardecky-api.mana.how
ports:
- "5180:5180"
healthcheck:
diff --git a/docker/nginx/landings.conf b/docker/nginx/landings.conf
index 2248d4d4b..f47fa7ac0 100644
--- a/docker/nginx/landings.conf
+++ b/docker/nginx/landings.conf
@@ -8,6 +8,24 @@ gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript application/javascript application/json image/svg+xml;
+# ============================================
+# Cardecky-Migration: 301-Redirects (2026-05-08)
+# Alte cards.mana.how / cards-api.mana.how Bookmarks weiterleiten,
+# bis sich Nutzer/externe Clients an die neue Domain gewöhnt haben.
+# Nach 6–12 Monaten kann das wieder raus.
+# ============================================
+server {
+ listen 80;
+ server_name cards.mana.how;
+ return 301 https://cardecky.mana.how$request_uri;
+}
+
+server {
+ listen 80;
+ server_name cards-api.mana.how;
+ return 301 https://cardecky-api.mana.how$request_uri;
+}
+
# Default server (catch-all → it.mana.how as homepage)
server {
listen 80 default_server;
@@ -71,11 +89,13 @@ server {
include /etc/nginx/snippets/landing-common.conf;
}
-# cards.mana.how — Cards Landing
+# cardecky.com — Cardecky Marketing-Landing (eigene .com-Domain).
+# Die App selbst läuft als SvelteKit-Container auf cardecky.mana.how
+# (siehe cloudflared-config.yml + docker-compose.macmini.yml `cards-web`).
server {
listen 80;
- server_name cards.mana.how;
- root /srv/landings/cards;
+ server_name cardecky.com;
+ root /srv/landings/cardecky;
index index.html;
include /etc/nginx/snippets/landing-common.conf;
}
diff --git a/docker/prometheus/prometheus.yml b/docker/prometheus/prometheus.yml
index 88f43842f..4e36df09f 100644
--- a/docker/prometheus/prometheus.yml
+++ b/docker/prometheus/prometheus.yml
@@ -273,8 +273,8 @@ scrape_configs:
- https://manavoxel.mana.how
# Memoro Standalone-Stack (Phase 2 mana e.V. Plattform-Migration)
- https://memoro.mana.how
- # Cards Standalone (Phase-1 Spinoff vom Unified-mana-Modul, 2026-05-06)
- - https://cards.mana.how
+ # Cardecky Standalone (Phase-1 Spinoff vom Unified-mana-Modul, 2026-05-06)
+ - https://cardecky.mana.how
# Who? Game (Standalone-Bun-Stack auf Mac Mini, native unter PM2)
- https://who.mana.how/cantina
# npm-Registry (mana e.V. Plattform-Repo, Verdaccio)
diff --git a/docs/ARCHITECTURE_MIGRATION_REPORT.md b/docs/ARCHITECTURE_MIGRATION_REPORT.md
index 4d3f8448b..6061383ba 100644
--- a/docs/ARCHITECTURE_MIGRATION_REPORT.md
+++ b/docs/ARCHITECTURE_MIGRATION_REPORT.md
@@ -79,7 +79,7 @@ In zwei intensiven Sessions wurde die gesamte Mana-Architektur von einem **API-f
| Calendar | 3003 | 119 | RRULE Expansion, ICS Import |
| Contacts | 3004 | 89 | Avatar Upload (S3), vCard Import |
| Picture | 3006 | 144 | Replicate Image Gen + S3 Upload |
-| Cards | 3009 | 130 | AI Deck/Card Generation |
+| Cardecky | 3009 | 130 | AI Deck/Card Generation |
| Mukke | 3010 | 106 | S3 Upload/Download URLs |
| Questions | 3011 | 121 | Web Research (mana-search) |
| Storage | 3016 | 117 | File Upload/Download + Versions |
@@ -322,7 +322,7 @@ mana-analytics (Hono, 475 LOC)
| 3 | Calendar | calendars, events | ✅ Komplett |
| 4 | Clock | alarms, timers, worldClocks | ✅ Komplett |
| 5 | Contacts | contacts | ✅ Komplett |
-| 6 | Cards | decks, cards | ✅ Komplett |
+| 6 | Cardecky | decks, cards | ✅ Komplett |
| 7 | Presi | decks, slides | ✅ Komplett |
| 8 | Picture | images, boards, boardItems, tags, imageTags | ✅ Komplett |
| 9 | Inventar | collections, items, locations, categories | ✅ Komplett |
diff --git a/docs/CARDS_POSTGRES_MIGRATION.md b/docs/CARDS_POSTGRES_MIGRATION.md
index f0216c120..65fd8d88e 100644
--- a/docs/CARDS_POSTGRES_MIGRATION.md
+++ b/docs/CARDS_POSTGRES_MIGRATION.md
@@ -1,8 +1,8 @@
-# Cards: Migration zu PostgreSQL + Drizzle ORM
+# Cardecky: Migration zu PostgreSQL + Drizzle ORM
## Übersicht
-Dieses Dokument beschreibt die Migration von Cards von Supabase zu einer selbst-gehosteten PostgreSQL-Datenbank mit Drizzle ORM.
+Dieses Dokument beschreibt die Migration von Cardecky von Supabase zu einer selbst-gehosteten PostgreSQL-Datenbank mit Drizzle ORM.
---
@@ -10,14 +10,14 @@ Dieses Dokument beschreibt die Migration von Cards von Supabase zu einer selbst-
```
┌─────────────────┐ ┌─────────────────┐
-│ Cards Web │ │ Cards Mobile │
+│ Cardecky Web │ │ Cardecky Mobile │
│ (SvelteKit) │ │ (Expo) │
└────────┬────────┘ └────────┬────────┘
│ │
└───────────┬───────────┘
│
┌───────────▼───────────┐
- │ Cards Backend │
+ │ Cardecky Backend │
│ (NestJS) │
└───────────┬───────────┘
│
@@ -36,14 +36,14 @@ Dieses Dokument beschreibt die Migration von Cards von Supabase zu einer selbst-
```
┌─────────────────┐ ┌─────────────────┐
-│ Cards Web │ │ Cards Mobile │
+│ Cardecky Web │ │ Cardecky Mobile │
│ (SvelteKit) │ │ (Expo) │
└────────┬────────┘ └────────┬────────┘
│ │
└───────────┬───────────┘
│
┌───────────▼───────────┐
- │ Cards Backend │
+ │ Cardecky Backend │
│ (NestJS + Drizzle) │
└───────────┬───────────┘
│
@@ -733,14 +733,14 @@ async function migrateDecks() {
console.log(`Migrated ${supabaseDecks.length} decks`);
}
-async function migrateCards() {
+async function migrateCardecky() {
console.log('Migrating cards...');
- const { data: supabaseCards, error } = await supabase.from('cards').select('*');
+ const { data: supabaseCardecky, error } = await supabase.from('cards').select('*');
if (error) throw error;
- for (const card of supabaseCards) {
+ for (const card of supabaseCardecky) {
await newDb
.insert(cards)
.values({
@@ -760,13 +760,13 @@ async function migrateCards() {
.onConflictDoNothing();
}
- console.log(`Migrated ${supabaseCards.length} cards`);
+ console.log(`Migrated ${supabaseCardecky.length} cards`);
}
async function main() {
try {
await migrateDecks();
- await migrateCards();
+ await migrateCardecky();
// ... andere Tabellen
console.log('Migration completed successfully!');
} catch (error) {
diff --git a/docs/CLOUDFLARE_DOMAINS.md b/docs/CLOUDFLARE_DOMAINS.md
index 74a8b58f8..0208ab728 100644
--- a/docs/CLOUDFLARE_DOMAINS.md
+++ b/docs/CLOUDFLARE_DOMAINS.md
@@ -109,7 +109,8 @@ Cloudflare Tunnel (bb0ea86d...)
| `quotess.mana.how` | Quotes Landing | `/srv/landings/quotes` |
| `presis.mana.how` | Presi Landing | `/srv/landings/presi` |
| `clocks.mana.how` | Clock Landing | `/srv/landings/clock` |
-| `cards.mana.how` | Cards Landing | `/srv/landings/cards` |
+| `cardecky.mana.how` | Cardecky App | (Docker container — not a static landing) |
+| `cardecky.com` | Cardecky Marketing-Landing | `/srv/landings/cardecky` |
| `food.mana.how` | Food Landing | `/srv/landings/food` |
| `citycorners.mana.how` | CityCorners Landing | `/srv/landings/citycorners` |
| `docs.mana.how` | Dokumentation | `/srv/landings/docs` |
diff --git a/docs/DEVELOPMENT_SCRIPTS.md b/docs/DEVELOPMENT_SCRIPTS.md
index 413d18ff7..19d1b52c7 100644
--- a/docs/DEVELOPMENT_SCRIPTS.md
+++ b/docs/DEVELOPMENT_SCRIPTS.md
@@ -50,7 +50,7 @@ Diese Befehle starten ein komplettes Projekt mit allen zugehörigen Apps und Dep
| ------------------------- | ------------------------------------------------------ |
| `pnpm maerchenzauber:dev` | Startet Maerchenzauber (Backend, Web, Mobile, Landing) |
| `pnpm mana:dev` | Startet Mana (Web, Mobile, Landing) |
-| `pnpm cards:dev` | Startet Cards (Web, Mobile, Landing) |
+| `pnpm cards:dev` | Startet Cardecky (Web, Mobile, Landing) |
| `pnpm memoro:dev` | Startet Memoro (Web, Mobile, Landing) |
## Turbo Filter
diff --git a/docs/ENVIRONMENT_VARIABLES.md b/docs/ENVIRONMENT_VARIABLES.md
index 70cc6dbb5..6896481b0 100644
--- a/docs/ENVIRONMENT_VARIABLES.md
+++ b/docs/ENVIRONMENT_VARIABLES.md
@@ -119,7 +119,7 @@ The generator then creates app-specific `.env` files with the correct prefixes f
|----------|-------------|---------|
| `ARTICLES_IMPORT_WORKER_DISABLED` | Set to `true` to skip starting the bulk-import worker on this apps/api instance. Useful for tests, or when running multiple apps/api replicas and you want to designate a specific one as the worker. The worker uses `pg_try_advisory_xact_lock` so multiple instances are safe by default — this env-var is the explicit opt-out. | `false` |
-### Cards Project
+### Cardecky Project
| Variable | Description | Default |
|----------|-------------|---------|
diff --git a/docs/EXTERNAL_SERVICES.md b/docs/EXTERNAL_SERVICES.md
index 71ee6ea07..81c518949 100644
--- a/docs/EXTERNAL_SERVICES.md
+++ b/docs/EXTERNAL_SERVICES.md
@@ -84,7 +84,7 @@ Legende: LOCAL = Self-hosted | CLOUD = Externer Dienst | FREE = Kostenlos
|--------|--------|-------|------------------|-------------------|
| **Replicate** | Picture | Bildgenerierung (Flux, SDXL, SD) | `PICTURE_REPLICATE_API_TOKEN` | €20-100/Monat |
| **OpenRouter** | Chat | Cloud LLMs (Claude, GPT, Llama, DeepSeek) | `OPENROUTER_API_KEY` | €10-50/Monat |
-| **Google Gemini** | Planta, Food, Cards | Vision & Text AI | `GEMINI_API_KEY`, `PLANTA_GEMINI_API_KEY` | €5-20/Monat |
+| **Google Gemini** | Planta, Food, Cardecky | Vision & Text AI | `GEMINI_API_KEY`, `PLANTA_GEMINI_API_KEY` | €5-20/Monat |
| **Azure OpenAI** | Chat (Docker) | GPT-4o via Azure | `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_API_KEY` | Optional |
| **Anthropic** | Mana Games | Claude API | `MANA_GAMES_ANTHROPIC_API_KEY` | Optional |
| **Ollama** | Chat | Lokale LLMs (Gemma 3, Llama) | `OLLAMA_URL` | ✅ Bereits lokal |
@@ -477,7 +477,7 @@ PICTURE_REPLICATE_API_TOKEN=r8_xxx
# OpenRouter (Chat)
OPENROUTER_API_KEY=sk-or-v1-xxx
-# Google Gemini (Planta, Food, Cards)
+# Google Gemini (Planta, Food, Cardecky)
GEMINI_API_KEY=AIza...
PLANTA_GEMINI_API_KEY=AIza...
FOOD_GEMINI_API_KEY=AIza...
diff --git a/docs/LOCAL_DEVELOPMENT.md b/docs/LOCAL_DEVELOPMENT.md
index dd16b4ad7..37e281861 100644
--- a/docs/LOCAL_DEVELOPMENT.md
+++ b/docs/LOCAL_DEVELOPMENT.md
@@ -49,7 +49,7 @@ These apps have server-side compute and support both `local` and `full` modes:
| Calendar | 3003 | 5179 | Yes | Yes |
| Contacts | 3004 | 5184 | Yes | Yes |
| Picture | 3006 | 5175 | Yes | Yes |
-| Cards | 3009 | 5176 | Yes | Yes |
+| Cardecky | 3009 | 5176 | Yes | Yes |
| Mukke | 3010 | 5180 | Yes | Yes |
| Questions | 3011 | 5111 | Yes | Yes |
| Storage | 3016 | 5185 | Yes | Yes |
diff --git a/docs/MANA_EARNING_SYSTEM.md b/docs/MANA_EARNING_SYSTEM.md
index 3dee410e7..64480866c 100644
--- a/docs/MANA_EARNING_SYSTEM.md
+++ b/docs/MANA_EARNING_SYSTEM.md
@@ -190,7 +190,7 @@ Ein User gilt als "verified" wenn MINDESTENS EINS zutrifft:
### Reward-Struktur
-#### Cards
+#### Cardecky
| Metrik | Threshold | Credits | Max/Monat |
|--------|-----------|---------|-----------|
@@ -566,7 +566,7 @@ CREATE TABLE community.bounty_pool (
- [ ] Create Admin endpoints für Manual Review Queue
- [ ] **Integration in Content Apps**
- - [ ] Cards: Track deck copies
+ - [ ] Cardecky: Track deck copies
- [ ] Quotes: Track quote submissions/views
- [ ] Presi: Track template usage
diff --git a/docs/MOBILE_DESKTOP_APP_STRATEGY.md b/docs/MOBILE_DESKTOP_APP_STRATEGY.md
index bdf296607..b081dca66 100644
--- a/docs/MOBILE_DESKTOP_APP_STRATEGY.md
+++ b/docs/MOBILE_DESKTOP_APP_STRATEGY.md
@@ -258,7 +258,7 @@ Unabhängig von der nativen Strategie: Service Worker und Web App Manifest hinzu
### React Native / Expo nur für dedizierte Einzel-Apps
-Die bestehenden Expo-Apps im Monorepo machen Sinn für Module, die eine fundamental native Mobile-UX brauchen (z.B. Cards mit Swipe-Gesten, Chat mit nativen Push Notifications). Für "die gesamte unified App auf Mobile bringen" ist der Aufwand (komplettes Rewrite) nicht verhältnismässig.
+Die bestehenden Expo-Apps im Monorepo machen Sinn für Module, die eine fundamental native Mobile-UX brauchen (z.B. Cardecky mit Swipe-Gesten, Chat mit nativen Push Notifications). Für "die gesamte unified App auf Mobile bringen" ist der Aufwand (komplettes Rewrite) nicht verhältnismässig.
### IndexedDB-Risiko mitigieren
diff --git a/docs/MODULE_REGISTRY.md b/docs/MODULE_REGISTRY.md
index 3ae2f8a1d..c4c67161c 100644
--- a/docs/MODULE_REGISTRY.md
+++ b/docs/MODULE_REGISTRY.md
@@ -42,7 +42,7 @@ Alle 76 Module der Mana-App (`apps/mana/apps/web/src/lib/modules/`).
| `picture` | ManaPicture | AI-Bildgenerierung |
| `photos` | Photos | Foto-Management mit Alben, Tags und Favoriten |
| `presi` | Presi | Präsentationen mit AI-Design-Vorschlägen |
-| `cards` | Cards | AI-generierte Flashcards mit Spaced Repetition |
+| `cards` | Cardecky | AI-generierte Flashcards mit Spaced Repetition |
| `recipes` | Rezepte | Rezepte sammeln mit Zutaten und Schritten |
| `library` | Bibliothek | Bücher, Filme, Serien, Comics — Status, Rating, Fortschritt |
| `storage` | Storage | Cloud-Storage mit Ordnern, Versionierung und Sharing |
diff --git a/docs/MONETIZATION_REPORT.md b/docs/MONETIZATION_REPORT.md
index 92ff496a7..3171ab999 100644
--- a/docs/MONETIZATION_REPORT.md
+++ b/docs/MONETIZATION_REPORT.md
@@ -46,7 +46,7 @@
- **Warum:** Reise-Apps monetarisieren gut. AI-generierte City Guides sind unique. Saisonaler Peak (Urlaubszeit), aber wiederkehrende Nutzung bei Vielreisenden.
- **Empfehlung:** Free (Tracking unlimited, 1 Guide/Stadt), Premium (unlimited Guides + Offline-Download).
-### 7. Cards (Deck/Flashcard Management)
+### 7. Cardecky (Deck/Flashcard Management)
- **Modell:** Credit-System vorhanden (10 Mana/Deck, 5/AI-Generation)
- **Warum:** Anki, Quizlet sind bewiesene Märkte. AI-Kartengeneration ist starker USP. Studenten zahlen für Lerntools (besonders vor Prüfungen).
diff --git a/docs/MONITORING.md b/docs/MONITORING.md
index 60d57794c..b682576f9 100644
--- a/docs/MONITORING.md
+++ b/docs/MONITORING.md
@@ -39,7 +39,7 @@ All monitoring tools are publicly accessible - no login required (except GlitchT
| Planta | https://stats.mana.how/share/1e83a8a67fa84d3995455c21dedbe3a2/plants-webapp |
| Presi | https://stats.mana.how/share/a1eb8d1fa4d543e6b97ac41351fe1c6f/presi-webapp |
| Skilltree | https://stats.mana.how/share/5de13e0895ae4a69aa2a834f985be14d/skilltree-webapp |
-| Cards | https://stats.mana.how/share/1c1d54c4782943e58dde0a6db7c86ec6/cards-webapp |
+| Cardecky | https://stats.mana.how/share/1c1d54c4782943e58dde0a6db7c86ec6/cards-webapp |
### GlitchTip Error Tracking
diff --git a/docs/PROJECT_OVERVIEW.md b/docs/PROJECT_OVERVIEW.md
index 65bd56b4d..2f07009c6 100644
--- a/docs/PROJECT_OVERVIEW.md
+++ b/docs/PROJECT_OVERVIEW.md
@@ -8,7 +8,7 @@ Dieses Dokument bietet eine umfassende Übersicht über alle Projekte im Manacor
2. [Projekte](#projekte)
- [Maerchenzauber](#maerchenzauber)
- [Manacore](#mana)
- - [Cards](#cards)
+ - [Cardecky](#cardecky)
- [Memoro](#memoro)
- [Picture](#picture)
- [uLoad](#uload)
@@ -129,7 +129,7 @@ Manacore ist die zentrale Plattform für Organisations-Management, Team-Kollabor
#### Unterstützte Apps
- **Memoro** - Sprachaufnahmen und Memory-Management
-- **Cards** - KI-gestützte Lernkarten
+- **Cardecky** - KI-gestützte Lernkarten
- **Storyteller** - Kreatives Schreiben mit KI
- **Mana** - Zentrale Account- und Organisationsverwaltung
@@ -155,11 +155,11 @@ mana/
---
-### Cards
+### Cardecky
**KI-gestütztes Lernkarten-System**
-Cards ist ein Deck-Management-System mit KI-gestützter Kartenerstellung und dem integrierten Mana Credit-System.
+Cardecky ist ein Deck-Management-System mit KI-gestützter Kartenerstellung und dem integrierten Mana Credit-System.
#### Features
@@ -489,7 +489,7 @@ Alle Projekte teilen gemeinsame Packages unter `packages/`:
| Package | Beschreibung |
| ------------------- | ------------------------- |
-| `cards-database` | Cards Datenbank-Schema |
+| `cards-database` | Cardecky Datenbank-Schema |
| `uload-database` | uLoad Datenbank-Schema |
### Verwendung
@@ -615,7 +615,7 @@ Siehe die jeweiligen CLAUDE.md Dateien in den Projektverzeichnissen für detaill
- [i18n](./I18N.md) - Internationalisierungs-Guide
- [Self-Hosting Guide](./SELF-HOSTING-GUIDE.md) - Self-Hosting Anleitung
- [uLoad Deployment](./ULOAD-DEPLOYMENT.md) - uLoad Deployment Guide
-- [Cards Postgres Migration](./CARDS_POSTGRES_MIGRATION.md) - Datenbank-Migration
+- [Cardecky Postgres Migration](./CARDS_POSTGRES_MIGRATION.md) - Datenbank-Migration
---
@@ -639,7 +639,7 @@ Dieser Abschnitt enthält durchdachte Ideen für neue Anwendungen, die das Manac
- Kreatives Schreiben (Maerchenzauber)
- Sprachaufnahmen & Transkription (Memoro)
-- Lernen & Wissensmanagement (Cards)
+- Lernen & Wissensmanagement (Cardecky)
- Bildbearbeitung (Picture)
- Link-Management (uLoad)
- Kommunikation (Chat)
@@ -664,7 +664,7 @@ Dieser Abschnitt enthält durchdachte Ideen für neue Anwendungen, die das Manac
**Synergien:**
- **Memoro-Integration:** Audio-Memos werden automatisch als Notizen importiert
-- **Cards-Integration:** Aus Notizen Lernkarten generieren
+- **Cardecky-Integration:** Aus Notizen Lernkarten generieren
- **Chat-Integration:** Chat-Verläufe als Notizen speichern
**Credit-Modell:**
@@ -910,7 +910,7 @@ Dieser Abschnitt enthält durchdachte Ideen für neue Anwendungen, die das Manac
**Synergien:**
-- **Cards-Integration:** Kursinhalte → Lernkarten
+- **Cardecky-Integration:** Kursinhalte → Lernkarten
- **Memoro-Integration:** Vorlesungen aufnehmen und transkribieren
- **ManaNote-Integration:** Kurs-Notizen
- **ManaVideo-Integration:** Video-Lektionen
@@ -943,7 +943,7 @@ Dieser Abschnitt enthält durchdachte Ideen für neue Anwendungen, die das Manac
**Synergien:**
-- **Cards-Integration:** Highlights → Lernkarten
+- **Cardecky-Integration:** Highlights → Lernkarten
- **ManaNote-Integration:** Exzerpte in Notizen überführen
- **ManaLearn-Integration:** Leselisten für Kurse
@@ -1403,7 +1403,7 @@ Basierend auf Synergien mit bestehenden Apps, Marktpotenzial und technischer Mac
| ------------- | ------------------------------------------------- |
| **ManaNote** | Natürliche Erweiterung von Memoro, hohe Synergien |
| **ManaWrite** | Nutzt bestehende KI-Infrastruktur, klarer Markt |
-| **ManaRead** | Ergänzt Cards perfekt, Bildungsmarkt |
+| **ManaRead** | Ergänzt Cardecky perfekt, Bildungsmarkt |
| **ManaMeet** | Memoro-Technologie wiederverwendbar |
#### Mittlere Priorität (Strategisch wichtig)
@@ -1414,7 +1414,7 @@ Basierend auf Synergien mit bestehenden Apps, Marktpotenzial und technischer Mac
| **ManaCalendar** | Verbindet alle Produktivitäts-Apps |
| **ManaPodcast** | Wachsender Markt, Memoro-Basis |
| **ManaDesign** | Picture erweitern, Marketing-Use-Cases |
-| **ManaLearn** | Cards + Memoro + Video kombinieren |
+| **ManaLearn** | Cardecky + Memoro + Video kombinieren |
#### Langfristig (Exploration)
@@ -1456,9 +1456,9 @@ Basierend auf Synergien mit bestehenden Apps, Marktpotenzial und technischer Mac
### Fazit
-Das Manacore-Ökosystem hat enormes Potenzial für Erweiterungen. Die bestehende Infrastruktur (Credits, Auth, Shared Packages, KI-Integration) ermöglicht schnelle Entwicklung neuer Apps. Der Fokus sollte zunächst auf Produktivitäts-Tools liegen, die starke Synergien mit Memoro und Cards haben.
+Das Manacore-Ökosystem hat enormes Potenzial für Erweiterungen. Die bestehende Infrastruktur (Credits, Auth, Shared Packages, KI-Integration) ermöglicht schnelle Entwicklung neuer Apps. Der Fokus sollte zunächst auf Produktivitäts-Tools liegen, die starke Synergien mit Memoro und Cardecky haben.
-Die Vision: **Ein zusammenhängendes Ökosystem, in dem Daten nahtlos zwischen Apps fließen** - von der Sprachaufnahme (Memoro) über Notizen (ManaNote) zu Lernkarten (Cards), mit KI-Unterstützung auf jedem Schritt.
+Die Vision: **Ein zusammenhängendes Ökosystem, in dem Daten nahtlos zwischen Apps fließen** - von der Sprachaufnahme (Memoro) über Notizen (ManaNote) zu Lernkarten (Cardecky), mit KI-Unterstützung auf jedem Schritt.
---
diff --git a/docs/SHARED_PACKAGES_ROADMAP.md b/docs/SHARED_PACKAGES_ROADMAP.md
index 351d66d18..b8f8c7bfa 100644
--- a/docs/SHARED_PACKAGES_ROADMAP.md
+++ b/docs/SHARED_PACKAGES_ROADMAP.md
@@ -69,7 +69,7 @@ All web apps now use the shared packages consistently:
- Mana Web
- Memoro Web
- Maerchenzauber Web
-- Cards Web
+- Cardecky Web
---
@@ -127,7 +127,7 @@ packages/shared-tailwind/
- Memoro Web (full migration with theme.css + components.css)
- Mana Web (preset only, keeps local colors)
-- Cards Web (colors import, HSL-based system)
+- Cardecky Web (colors import, HSL-based system)
- Maerchenzauber Web (dependency added)
---
diff --git a/docs/TECHNOLOGY_AUDIT_2026_03.md b/docs/TECHNOLOGY_AUDIT_2026_03.md
index 183b252a5..79c279aa0 100644
--- a/docs/TECHNOLOGY_AUDIT_2026_03.md
+++ b/docs/TECHNOLOGY_AUDIT_2026_03.md
@@ -124,7 +124,7 @@ Da bereits **mana-sync (Go)** als zentraler Sync-Server existiert und **local-fi
- Schwer (~50 MB node_modules pro Backend)
- Viel Boilerplate (Module, Controller, Service, DTO fuer jede Entity)
- Overkill fuer simple CRUD-Operationen
-- NestJS Version-Drift: 18 Backends auf ^10.4.x, Cards auf ^11.0.1
+- NestJS Version-Drift: 18 Backends auf ^10.4.x, Cardecky auf ^11.0.1
### Empfehlung
diff --git a/docs/TECH_STACK_INDEPENDENCE.md b/docs/TECH_STACK_INDEPENDENCE.md
index 2e7c00dda..27860b201 100644
--- a/docs/TECH_STACK_INDEPENDENCE.md
+++ b/docs/TECH_STACK_INDEPENDENCE.md
@@ -97,10 +97,10 @@ Brevo ist SPOF für alle Transaktions-Emails (Verifizierung, Passwort-Reset).
**Status: ✅ ERLEDIGT** (2026-03-24)
Alle 9 Backends nutzen jetzt `@mana/shared-llm` → `mana-llm` Gateway:
-- Auth, Chat, Context, Food, Planta, Traces, Cards, Bot Services, Matrix Bots
+- Auth, Chat, Context, Food, Planta, Traces, Cardecky, Bot Services, Matrix Bots
- Google Gemini als automatischer Fallback wenn Ollama überlastet
- OpenAI SDK komplett entfernt (Project Doc Bot)
-- Google Gemini SDK entfernt (Cards)
+- Google Gemini SDK entfernt (Cardecky)
#### 2.2 PostgreSQL Backup stärken
@@ -219,7 +219,7 @@ Food und Planta nutzen Google Gemini Vision. Alternativen via Ollama:
- ✅ Prio 1: Picture App nutzt lokales `mana-image-gen` (FLUX.2 klein) als Default
- ✅ Prio 2: Project Doc Bot: OpenAI SDK komplett entfernt, nutzt mana-llm + mana-stt
- ✅ Prio 3: Alle LLM-Calls über `mana-llm` geroutet (10 Backends, `@mana/shared-llm`)
-- ✅ Prio 3: Google Gemini Fallback in mana-llm + Cards Gemini SDK entfernt
+- ✅ Prio 3: Google Gemini Fallback in mana-llm + Cardecky Gemini SDK entfernt
- ✅ Prio 4: PostgreSQL Backup mit stündlichen Dumps + täglichen Base-Backups
- ✅ Prio 6: Landing Pages self-hosted via Nginx (10 Domains, kein Cloudflare Pages mehr)
- ✅ Prio 8: Cloudflare-Fallback Plan dokumentiert (WireGuard + Caddy)
diff --git a/docs/USER_SETTINGS.md b/docs/USER_SETTINGS.md
index c06ef9cdc..ccfd8ff86 100644
--- a/docs/USER_SETTINGS.md
+++ b/docs/USER_SETTINGS.md
@@ -324,7 +324,7 @@ Folgende Apps nutzen bereits die zentralen User Settings:
- Chat (`chat`)
- Contacts (`contacts`)
- Mana (`mana`)
-- Cards (`cards`)
+- Cardecky (`cards`)
- Picture (`picture`)
- Presi (`presi`)
- Storage (`storage`)
diff --git a/docs/VERSIONING.md b/docs/VERSIONING.md
index e66845889..0fc81d1cc 100644
--- a/docs/VERSIONING.md
+++ b/docs/VERSIONING.md
@@ -36,7 +36,7 @@ All apps follow `MAJOR.MINOR.PATCH`:
| Quotes | 0.2.0 | Beta |
| Clock | 0.2.0 | Beta |
| Food | 0.2.0 | Beta |
-| Cards | 0.2.0 | Beta |
+| Cardecky | 0.2.0 | Beta |
| Mana | 0.2.0 | Beta |
| Matrix | 0.2.0 | Beta |
| Photos | 0.2.0 | Beta |
diff --git a/docs/architecture/COMPANION_BRAIN_ARCHITECTURE.md b/docs/architecture/COMPANION_BRAIN_ARCHITECTURE.md
index efe0bed27..53fc92a05 100644
--- a/docs/architecture/COMPANION_BRAIN_ARCHITECTURE.md
+++ b/docs/architecture/COMPANION_BRAIN_ARCHITECTURE.md
@@ -1386,7 +1386,7 @@ Phase 1 (Events) ──────┬──> Phase 2 (Projections)
| 10 | Body | 5 | 3 | Batch 2 |
| 11 | Finance | 2 | 1 | Batch 3 |
| 12 | Dreams | 2 | 1 | Batch 3 |
-| 13 | Cards | 2 | 1 | Batch 3 |
+| 13 | Cardecky | 2 | 1 | Batch 3 |
| 14 | Times | 2 | 2 | Batch 3 |
| 15 | Social Events | 2 | 1 | Batch 3 |
| 16 | Music | 1 | 1 | Batch 4 |
diff --git a/packages/cards-core/package.json b/packages/cards-core/package.json
index 3743a16ad..4c9e2364d 100644
--- a/packages/cards-core/package.json
+++ b/packages/cards-core/package.json
@@ -3,7 +3,7 @@
"version": "0.1.0",
"private": true,
"sideEffects": false,
- "description": "Pure utilities for the Cards product: types, FSRS wrapper, Cloze parser, Markdown render. Consumed by both the mana cards module and the cards.mana.how standalone app.",
+ "description": "Pure utilities for the Cardecky product: types, FSRS wrapper, Cloze parser, Markdown render. Consumed by both the mana cards module and the cardecky.mana.how standalone app.",
"main": "./src/index.ts",
"types": "./src/index.ts",
"exports": {
diff --git a/packages/cards-core/src/index.ts b/packages/cards-core/src/index.ts
index d16da479a..4e671e51f 100644
--- a/packages/cards-core/src/index.ts
+++ b/packages/cards-core/src/index.ts
@@ -1,6 +1,6 @@
/**
- * Cards-Core — pure utilities used by both the mana cards module
- * (apps/mana/.../modules/cards/) and the cards.mana.how standalone app.
+ * Cardecky / Cards-Core — pure utilities used by both the mana cards module
+ * (apps/mana/.../modules/cards/) and the cardecky.mana.how standalone app.
*
* Only DB-free code lives here. Anything that touches Dexie, mana-sync,
* or app-specific encryption stays in the consumer apps.
diff --git a/packages/cards-core/src/types.ts b/packages/cards-core/src/types.ts
index f686d8a46..ad1e1a29e 100644
--- a/packages/cards-core/src/types.ts
+++ b/packages/cards-core/src/types.ts
@@ -1,8 +1,8 @@
/**
- * Cards — shared types.
+ * Cardecky — shared types.
*
* Used by both the mana cards module (apps/mana/.../modules/cards/) and
- * the cards.mana.how standalone app. Pure type definitions, no runtime
+ * the cardecky.mana.how standalone app. Pure type definitions, no runtime
* imports beyond `BaseRecord` and `VisibilityLevel` from the shared
* Mana packages.
*/
@@ -49,7 +49,7 @@ export interface LocalDeck extends BaseRecord {
/**
* Marketplace-subscription markers. Set on decks that the user
- * pulled from cards.mana.how/d/ rather than created
+ * pulled from cardecky.mana.how/d/ rather than created
* themselves. The pair (slug + version) lets the client compute
* a smart-merge diff against the server's latest version.
*
diff --git a/packages/shared-branding/src/mana-apps.ts b/packages/shared-branding/src/mana-apps.ts
index 0c19c9868..e2856d244 100644
--- a/packages/shared-branding/src/mana-apps.ts
+++ b/packages/shared-branding/src/mana-apps.ts
@@ -155,7 +155,7 @@ export const MANA_APPS: ManaApp[] = [
},
{
id: 'cards',
- name: 'Cards',
+ name: 'Cardecky',
description: {
de: 'KI Karteikarten',
en: 'AI Flashcards',
diff --git a/services/cards-server/CLAUDE.md b/services/cards-server/CLAUDE.md
index 31f09c5b3..521be6460 100644
--- a/services/cards-server/CLAUDE.md
+++ b/services/cards-server/CLAUDE.md
@@ -1,9 +1,9 @@
# cards-server
-Cards Marketplace + Community backend. Owns the published-deck side of
-the Cards product (the standalone app at `cards.mana.how` is the
-client). Phase α is the data skeleton — schema + bootstrap + JWT auth
-in place; routes land progressively in Phase β onwards.
+Cardecky Marketplace + Community backend. Owns the published-deck side
+of the Cardecky product (the standalone app at `cardecky.mana.how` is
+the client). Phase α is the data skeleton — schema + bootstrap + JWT
+auth in place; routes land progressively in Phase β onwards.
For the full design rationale, phasing, and contract decisions see
**[`apps/cards/docs/MARKETPLACE_PLAN.md`](../../apps/cards/docs/MARKETPLACE_PLAN.md)**.
diff --git a/services/cards-server/drizzle/0001_rename_license_ids_to_cardecky.sql b/services/cards-server/drizzle/0001_rename_license_ids_to_cardecky.sql
new file mode 100644
index 000000000..25ed1db64
--- /dev/null
+++ b/services/cards-server/drizzle/0001_rename_license_ids_to_cardecky.sql
@@ -0,0 +1,23 @@
+-- Rename SPDX-style license identifiers from `Cards-*` to `Cardecky-*`.
+-- Folgt dem Brand-Rename des Produkts (Cards → Cardecky, 2026-05-08).
+-- Phase α (Skelett geshipt 2026-05-07) hat ggf. wenige Datensätze.
+--
+-- Reihenfolge: erst CHECK droppen, dann UPDATE, dann CHECK neu setzen.
+-- Sonst feuert die alte Constraint beim UPDATE der Pro-Only-Decks.
+
+ALTER TABLE "cards"."decks" DROP CONSTRAINT "decks_price_requires_license";
+
+UPDATE "cards"."decks"
+SET "license" = 'Cardecky-Personal-Use-1.0'
+WHERE "license" = 'Cards-Personal-Use-1.0';
+
+UPDATE "cards"."decks"
+SET "license" = 'Cardecky-Pro-Only-1.0'
+WHERE "license" = 'Cards-Pro-Only-1.0';
+
+ALTER TABLE "cards"."decks"
+ALTER COLUMN "license" SET DEFAULT 'Cardecky-Personal-Use-1.0';
+
+ALTER TABLE "cards"."decks"
+ADD CONSTRAINT "decks_price_requires_license"
+CHECK (price_credits = 0 OR license = 'Cardecky-Pro-Only-1.0');
diff --git a/services/cards-server/drizzle/meta/0001_snapshot.json b/services/cards-server/drizzle/meta/0001_snapshot.json
new file mode 100644
index 000000000..97efe503b
--- /dev/null
+++ b/services/cards-server/drizzle/meta/0001_snapshot.json
@@ -0,0 +1,1910 @@
+{
+ "id": "9b3e7f1c-2a48-4d6b-8c5e-0f1a3d7c8e9b",
+ "prevId": "dc92bce1-ef98-41fa-97f1-0a6d1512bcdb",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "cards.author_follows": {
+ "name": "author_follows",
+ "schema": "cards",
+ "columns": {
+ "follower_user_id": {
+ "name": "follower_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "author_user_id": {
+ "name": "author_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "since": {
+ "name": "since",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "author_follows_pk": {
+ "name": "author_follows_pk",
+ "columns": [
+ {
+ "expression": "follower_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "author_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "author_follows_author_idx": {
+ "name": "author_follows_author_idx",
+ "columns": [
+ {
+ "expression": "author_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "author_follows_follower_idx": {
+ "name": "author_follows_follower_idx",
+ "columns": [
+ {
+ "expression": "follower_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "author_follows_author_user_id_authors_user_id_fk": {
+ "name": "author_follows_author_user_id_authors_user_id_fk",
+ "tableFrom": "author_follows",
+ "tableTo": "authors",
+ "schemaTo": "cards",
+ "columnsFrom": ["author_user_id"],
+ "columnsTo": ["user_id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "cards.authors": {
+ "name": "authors",
+ "schema": "cards",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "slug": {
+ "name": "slug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "display_name": {
+ "name": "display_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bio": {
+ "name": "bio",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "avatar_url": {
+ "name": "avatar_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "joined_at": {
+ "name": "joined_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "pseudonym": {
+ "name": "pseudonym",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "verified_mana": {
+ "name": "verified_mana",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "verified_community": {
+ "name": "verified_community",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "banned_at": {
+ "name": "banned_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "banned_reason": {
+ "name": "banned_reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "authors_slug_idx": {
+ "name": "authors_slug_idx",
+ "columns": [
+ {
+ "expression": "slug",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "authors_verified_idx": {
+ "name": "authors_verified_idx",
+ "columns": [
+ {
+ "expression": "verified_mana",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "verified_community",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "cards.author_payouts": {
+ "name": "author_payouts",
+ "schema": "cards",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "author_user_id": {
+ "name": "author_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_purchase_id": {
+ "name": "source_purchase_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "credits_granted": {
+ "name": "credits_granted",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "credits_grant_id": {
+ "name": "credits_grant_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "granted_at": {
+ "name": "granted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "author_payouts_author_idx": {
+ "name": "author_payouts_author_idx",
+ "columns": [
+ {
+ "expression": "author_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "author_payouts_purchase_idx": {
+ "name": "author_payouts_purchase_idx",
+ "columns": [
+ {
+ "expression": "source_purchase_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "author_payouts_author_user_id_authors_user_id_fk": {
+ "name": "author_payouts_author_user_id_authors_user_id_fk",
+ "tableFrom": "author_payouts",
+ "tableTo": "authors",
+ "schemaTo": "cards",
+ "columnsFrom": ["author_user_id"],
+ "columnsTo": ["user_id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ },
+ "author_payouts_source_purchase_id_deck_purchases_id_fk": {
+ "name": "author_payouts_source_purchase_id_deck_purchases_id_fk",
+ "tableFrom": "author_payouts",
+ "tableTo": "deck_purchases",
+ "schemaTo": "cards",
+ "columnsFrom": ["source_purchase_id"],
+ "columnsTo": ["id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "cards.deck_purchases": {
+ "name": "deck_purchases",
+ "schema": "cards",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "buyer_user_id": {
+ "name": "buyer_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "deck_id": {
+ "name": "deck_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "version_id": {
+ "name": "version_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "price_credits": {
+ "name": "price_credits",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "author_share": {
+ "name": "author_share",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "mana_share": {
+ "name": "mana_share",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "credits_transaction": {
+ "name": "credits_transaction",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "purchased_at": {
+ "name": "purchased_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "refunded_at": {
+ "name": "refunded_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "deck_purchases_buyer_deck_idx": {
+ "name": "deck_purchases_buyer_deck_idx",
+ "columns": [
+ {
+ "expression": "buyer_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "deck_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "deck_purchases_buyer_idx": {
+ "name": "deck_purchases_buyer_idx",
+ "columns": [
+ {
+ "expression": "buyer_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "deck_purchases_deck_idx": {
+ "name": "deck_purchases_deck_idx",
+ "columns": [
+ {
+ "expression": "deck_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "deck_purchases_deck_id_decks_id_fk": {
+ "name": "deck_purchases_deck_id_decks_id_fk",
+ "tableFrom": "deck_purchases",
+ "tableTo": "decks",
+ "schemaTo": "cards",
+ "columnsFrom": ["deck_id"],
+ "columnsTo": ["id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ },
+ "deck_purchases_version_id_deck_versions_id_fk": {
+ "name": "deck_purchases_version_id_deck_versions_id_fk",
+ "tableFrom": "deck_purchases",
+ "tableTo": "deck_versions",
+ "schemaTo": "cards",
+ "columnsFrom": ["version_id"],
+ "columnsTo": ["id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "cards.deck_cards": {
+ "name": "deck_cards",
+ "schema": "cards",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "version_id": {
+ "name": "version_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "cards_card_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "fields": {
+ "name": "fields",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ord": {
+ "name": "ord",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "content_hash": {
+ "name": "content_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "deck_cards_version_ord_idx": {
+ "name": "deck_cards_version_ord_idx",
+ "columns": [
+ {
+ "expression": "version_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "ord",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "deck_cards_hash_idx": {
+ "name": "deck_cards_hash_idx",
+ "columns": [
+ {
+ "expression": "content_hash",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "deck_cards_version_id_deck_versions_id_fk": {
+ "name": "deck_cards_version_id_deck_versions_id_fk",
+ "tableFrom": "deck_cards",
+ "tableTo": "deck_versions",
+ "schemaTo": "cards",
+ "columnsFrom": ["version_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "cards.deck_versions": {
+ "name": "deck_versions",
+ "schema": "cards",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "deck_id": {
+ "name": "deck_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "semver": {
+ "name": "semver",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "changelog": {
+ "name": "changelog",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "content_hash": {
+ "name": "content_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "card_count": {
+ "name": "card_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "published_at": {
+ "name": "published_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "deprecated_at": {
+ "name": "deprecated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "deck_versions_deck_semver_idx": {
+ "name": "deck_versions_deck_semver_idx",
+ "columns": [
+ {
+ "expression": "deck_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "semver",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "deck_versions_deck_idx": {
+ "name": "deck_versions_deck_idx",
+ "columns": [
+ {
+ "expression": "deck_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "deck_versions_hash_idx": {
+ "name": "deck_versions_hash_idx",
+ "columns": [
+ {
+ "expression": "content_hash",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "deck_versions_deck_id_decks_id_fk": {
+ "name": "deck_versions_deck_id_decks_id_fk",
+ "tableFrom": "deck_versions",
+ "tableTo": "decks",
+ "schemaTo": "cards",
+ "columnsFrom": ["deck_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "cards.decks": {
+ "name": "decks",
+ "schema": "cards",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "slug": {
+ "name": "slug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "language": {
+ "name": "language",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "license": {
+ "name": "license",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'Cardecky-Personal-Use-1.0'"
+ },
+ "price_credits": {
+ "name": "price_credits",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "latest_version_id": {
+ "name": "latest_version_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_featured": {
+ "name": "is_featured",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "is_takedown": {
+ "name": "is_takedown",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "takedown_at": {
+ "name": "takedown_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "takedown_reason": {
+ "name": "takedown_reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "decks_slug_idx": {
+ "name": "decks_slug_idx",
+ "columns": [
+ {
+ "expression": "slug",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "decks_owner_idx": {
+ "name": "decks_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "decks_featured_idx": {
+ "name": "decks_featured_idx",
+ "columns": [
+ {
+ "expression": "is_featured",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "decks_owner_user_id_authors_user_id_fk": {
+ "name": "decks_owner_user_id_authors_user_id_fk",
+ "tableFrom": "decks",
+ "tableTo": "authors",
+ "schemaTo": "cards",
+ "columnsFrom": ["owner_user_id"],
+ "columnsTo": ["user_id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "decks_price_requires_license": {
+ "name": "decks_price_requires_license",
+ "value": "price_credits = 0 OR license = 'Cardecky-Pro-Only-1.0'"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "cards.card_discussions": {
+ "name": "card_discussions",
+ "schema": "cards",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "card_content_hash": {
+ "name": "card_content_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "deck_id": {
+ "name": "deck_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "author_user_id": {
+ "name": "author_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "parent_id": {
+ "name": "parent_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "body": {
+ "name": "body",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "hidden": {
+ "name": "hidden",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "card_discussions_hash_idx": {
+ "name": "card_discussions_hash_idx",
+ "columns": [
+ {
+ "expression": "card_content_hash",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "card_discussions_deck_idx": {
+ "name": "card_discussions_deck_idx",
+ "columns": [
+ {
+ "expression": "deck_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "card_discussions_parent_idx": {
+ "name": "card_discussions_parent_idx",
+ "columns": [
+ {
+ "expression": "parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "card_discussions_deck_id_decks_id_fk": {
+ "name": "card_discussions_deck_id_decks_id_fk",
+ "tableFrom": "card_discussions",
+ "tableTo": "decks",
+ "schemaTo": "cards",
+ "columnsFrom": ["deck_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "cards.deck_pull_requests": {
+ "name": "deck_pull_requests",
+ "schema": "cards",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "deck_id": {
+ "name": "deck_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "author_user_id": {
+ "name": "author_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "cards_pr_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'open'"
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "body": {
+ "name": "body",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "diff": {
+ "name": "diff",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "merged_into_version_id": {
+ "name": "merged_into_version_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "resolved_at": {
+ "name": "resolved_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "deck_pull_requests_deck_idx": {
+ "name": "deck_pull_requests_deck_idx",
+ "columns": [
+ {
+ "expression": "deck_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "deck_pull_requests_status_idx": {
+ "name": "deck_pull_requests_status_idx",
+ "columns": [
+ {
+ "expression": "deck_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "deck_pull_requests_author_idx": {
+ "name": "deck_pull_requests_author_idx",
+ "columns": [
+ {
+ "expression": "author_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "deck_pull_requests_deck_id_decks_id_fk": {
+ "name": "deck_pull_requests_deck_id_decks_id_fk",
+ "tableFrom": "deck_pull_requests",
+ "tableTo": "decks",
+ "schemaTo": "cards",
+ "columnsFrom": ["deck_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "deck_pull_requests_merged_into_version_id_deck_versions_id_fk": {
+ "name": "deck_pull_requests_merged_into_version_id_deck_versions_id_fk",
+ "tableFrom": "deck_pull_requests",
+ "tableTo": "deck_versions",
+ "schemaTo": "cards",
+ "columnsFrom": ["merged_into_version_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "cards.deck_forks": {
+ "name": "deck_forks",
+ "schema": "cards",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_deck_id": {
+ "name": "source_deck_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_version_id": {
+ "name": "source_version_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "forked_at": {
+ "name": "forked_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "deck_forks_pk": {
+ "name": "deck_forks_pk",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "source_deck_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "source_version_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "deck_forks_source_idx": {
+ "name": "deck_forks_source_idx",
+ "columns": [
+ {
+ "expression": "source_deck_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "deck_forks_source_deck_id_decks_id_fk": {
+ "name": "deck_forks_source_deck_id_decks_id_fk",
+ "tableFrom": "deck_forks",
+ "tableTo": "decks",
+ "schemaTo": "cards",
+ "columnsFrom": ["source_deck_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "deck_forks_source_version_id_deck_versions_id_fk": {
+ "name": "deck_forks_source_version_id_deck_versions_id_fk",
+ "tableFrom": "deck_forks",
+ "tableTo": "deck_versions",
+ "schemaTo": "cards",
+ "columnsFrom": ["source_version_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "cards.deck_stars": {
+ "name": "deck_stars",
+ "schema": "cards",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "deck_id": {
+ "name": "deck_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "starred_at": {
+ "name": "starred_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "deck_stars_pk": {
+ "name": "deck_stars_pk",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "deck_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "deck_stars_deck_idx": {
+ "name": "deck_stars_deck_idx",
+ "columns": [
+ {
+ "expression": "deck_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "deck_stars_deck_id_decks_id_fk": {
+ "name": "deck_stars_deck_id_decks_id_fk",
+ "tableFrom": "deck_stars",
+ "tableTo": "decks",
+ "schemaTo": "cards",
+ "columnsFrom": ["deck_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "cards.deck_subscriptions": {
+ "name": "deck_subscriptions",
+ "schema": "cards",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "deck_id": {
+ "name": "deck_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "current_version_id": {
+ "name": "current_version_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "subscribed_at": {
+ "name": "subscribed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "notify_updates": {
+ "name": "notify_updates",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ }
+ },
+ "indexes": {
+ "deck_subscriptions_pk": {
+ "name": "deck_subscriptions_pk",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "deck_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "deck_subscriptions_deck_idx": {
+ "name": "deck_subscriptions_deck_idx",
+ "columns": [
+ {
+ "expression": "deck_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "deck_subscriptions_user_idx": {
+ "name": "deck_subscriptions_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "deck_subscriptions_deck_id_decks_id_fk": {
+ "name": "deck_subscriptions_deck_id_decks_id_fk",
+ "tableFrom": "deck_subscriptions",
+ "tableTo": "decks",
+ "schemaTo": "cards",
+ "columnsFrom": ["deck_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "deck_subscriptions_current_version_id_deck_versions_id_fk": {
+ "name": "deck_subscriptions_current_version_id_deck_versions_id_fk",
+ "tableFrom": "deck_subscriptions",
+ "tableTo": "deck_versions",
+ "schemaTo": "cards",
+ "columnsFrom": ["current_version_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "cards.deck_tags": {
+ "name": "deck_tags",
+ "schema": "cards",
+ "columns": {
+ "deck_id": {
+ "name": "deck_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tag_id": {
+ "name": "tag_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "deck_tags_pk": {
+ "name": "deck_tags_pk",
+ "columns": [
+ {
+ "expression": "deck_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "tag_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "deck_tags_tag_idx": {
+ "name": "deck_tags_tag_idx",
+ "columns": [
+ {
+ "expression": "tag_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "deck_tags_deck_id_decks_id_fk": {
+ "name": "deck_tags_deck_id_decks_id_fk",
+ "tableFrom": "deck_tags",
+ "tableTo": "decks",
+ "schemaTo": "cards",
+ "columnsFrom": ["deck_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "deck_tags_tag_id_tag_definitions_id_fk": {
+ "name": "deck_tags_tag_id_tag_definitions_id_fk",
+ "tableFrom": "deck_tags",
+ "tableTo": "tag_definitions",
+ "schemaTo": "cards",
+ "columnsFrom": ["tag_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "cards.tag_definitions": {
+ "name": "tag_definitions",
+ "schema": "cards",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "slug": {
+ "name": "slug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "parent_id": {
+ "name": "parent_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "curated": {
+ "name": "curated",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "tag_definitions_slug_idx": {
+ "name": "tag_definitions_slug_idx",
+ "columns": [
+ {
+ "expression": "slug",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "tag_definitions_parent_idx": {
+ "name": "tag_definitions_parent_idx",
+ "columns": [
+ {
+ "expression": "parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "cards.ai_moderation_log": {
+ "name": "ai_moderation_log",
+ "schema": "cards",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "version_id": {
+ "name": "version_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "verdict": {
+ "name": "verdict",
+ "type": "cards_ai_mod_verdict",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "categories": {
+ "name": "categories",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "model": {
+ "name": "model",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rationale": {
+ "name": "rationale",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "human_reviewed": {
+ "name": "human_reviewed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "human_overrode": {
+ "name": "human_overrode",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "ai_moderation_log_version_idx": {
+ "name": "ai_moderation_log_version_idx",
+ "columns": [
+ {
+ "expression": "version_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "ai_moderation_log_verdict_idx": {
+ "name": "ai_moderation_log_verdict_idx",
+ "columns": [
+ {
+ "expression": "verdict",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "ai_moderation_log_version_id_deck_versions_id_fk": {
+ "name": "ai_moderation_log_version_id_deck_versions_id_fk",
+ "tableFrom": "ai_moderation_log",
+ "tableTo": "deck_versions",
+ "schemaTo": "cards",
+ "columnsFrom": ["version_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "cards.deck_reports": {
+ "name": "deck_reports",
+ "schema": "cards",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "deck_id": {
+ "name": "deck_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "version_id": {
+ "name": "version_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "card_content_hash": {
+ "name": "card_content_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "reporter_user_id": {
+ "name": "reporter_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "category": {
+ "name": "category",
+ "type": "cards_report_category",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "body": {
+ "name": "body",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "cards_report_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'open'"
+ },
+ "resolved_by": {
+ "name": "resolved_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "resolved_at": {
+ "name": "resolved_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "resolution_notes": {
+ "name": "resolution_notes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "deck_reports_deck_idx": {
+ "name": "deck_reports_deck_idx",
+ "columns": [
+ {
+ "expression": "deck_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "deck_reports_status_idx": {
+ "name": "deck_reports_status_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "deck_reports_deck_id_decks_id_fk": {
+ "name": "deck_reports_deck_id_decks_id_fk",
+ "tableFrom": "deck_reports",
+ "tableTo": "decks",
+ "schemaTo": "cards",
+ "columnsFrom": ["deck_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "deck_reports_version_id_deck_versions_id_fk": {
+ "name": "deck_reports_version_id_deck_versions_id_fk",
+ "tableFrom": "deck_reports",
+ "tableTo": "deck_versions",
+ "schemaTo": "cards",
+ "columnsFrom": ["version_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {
+ "public.cards_card_type": {
+ "name": "cards_card_type",
+ "schema": "public",
+ "values": [
+ "basic",
+ "basic-reverse",
+ "cloze",
+ "type-in",
+ "image-occlusion",
+ "audio",
+ "multiple-choice"
+ ]
+ },
+ "public.cards_pr_status": {
+ "name": "cards_pr_status",
+ "schema": "public",
+ "values": ["open", "merged", "closed", "rejected"]
+ },
+ "public.cards_ai_mod_verdict": {
+ "name": "cards_ai_mod_verdict",
+ "schema": "public",
+ "values": ["pass", "flag", "block"]
+ },
+ "public.cards_report_category": {
+ "name": "cards_report_category",
+ "schema": "public",
+ "values": ["spam", "copyright", "nsfw", "misinformation", "hate", "other"]
+ },
+ "public.cards_report_status": {
+ "name": "cards_report_status",
+ "schema": "public",
+ "values": ["open", "dismissed", "actioned"]
+ }
+ },
+ "schemas": {
+ "cards": "cards"
+ },
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
diff --git a/services/cards-server/drizzle/meta/_journal.json b/services/cards-server/drizzle/meta/_journal.json
index ea121bb4c..d3e78699b 100644
--- a/services/cards-server/drizzle/meta/_journal.json
+++ b/services/cards-server/drizzle/meta/_journal.json
@@ -8,6 +8,13 @@
"when": 1778162392774,
"tag": "0000_condemned_wrecking_crew",
"breakpoints": true
+ },
+ {
+ "idx": 1,
+ "version": "7",
+ "when": 1746662400000,
+ "tag": "0001_rename_license_ids_to_cardecky",
+ "breakpoints": true
}
]
}
diff --git a/services/cards-server/src/db/schema/decks.ts b/services/cards-server/src/db/schema/decks.ts
index d2bafba94..dcdc9f9ba 100644
--- a/services/cards-server/src/db/schema/decks.ts
+++ b/services/cards-server/src/db/schema/decks.ts
@@ -9,7 +9,7 @@
* version bumps.
*
* `price_credits` of 0 means free. Anything > 0 forces the
- * Cards-Pro-Only-1.0 license (CHECK constraint enforced at DB level).
+ * Cardecky-Pro-Only-1.0 license (CHECK constraint enforced at DB level).
*/
import {
@@ -49,8 +49,8 @@ export const publicDecks = cardsSchema.table(
// ISO-639-1 (e.g. 'de', 'en', 'es'). Nullable for mixed-language decks.
language: text('language'),
// SPDX-style ID. CC0-1.0, CC-BY-4.0, CC-BY-SA-4.0,
- // Cards-Personal-Use-1.0 (default for free), Cards-Pro-Only-1.0 (paid).
- license: text('license').notNull().default('Cards-Personal-Use-1.0'),
+ // Cardecky-Personal-Use-1.0 (default for free), Cardecky-Pro-Only-1.0 (paid).
+ license: text('license').notNull().default('Cardecky-Personal-Use-1.0'),
priceCredits: integer('price_credits').notNull().default(0),
ownerUserId: text('owner_user_id')
.notNull()
@@ -70,7 +70,7 @@ export const publicDecks = cardsSchema.table(
// Paid decks must carry the Pro-Only license.
priceLicense: check(
'decks_price_requires_license',
- sql`price_credits = 0 OR license = 'Cards-Pro-Only-1.0'`
+ sql`price_credits = 0 OR license = 'Cardecky-Pro-Only-1.0'`
),
})
);
diff --git a/services/cards-server/src/middleware/optional-auth.ts b/services/cards-server/src/middleware/optional-auth.ts
index f503c856f..f8c31b35d 100644
--- a/services/cards-server/src/middleware/optional-auth.ts
+++ b/services/cards-server/src/middleware/optional-auth.ts
@@ -7,7 +7,7 @@
*
* Why a separate middleware? `jwtAuth` is the strict gate for write
* paths — same JWKS, same algo, but rejecting early. `optionalAuth`
- * is the read-path companion: it lets cards-api.mana.how serve the
+ * is the read-path companion: it lets cardecky-api.mana.how serve the
* marketplace surface to anonymous browsers (search engines, anti-
* link-rot, share-link previews) while still recognising signed-in
* users for star/follow state.
diff --git a/services/cards-server/src/services/decks.ts b/services/cards-server/src/services/decks.ts
index 2b37e5a46..6d5d53da1 100644
--- a/services/cards-server/src/services/decks.ts
+++ b/services/cards-server/src/services/decks.ts
@@ -56,8 +56,8 @@ const SEMVER_RE = /^(\d+)\.(\d+)\.(\d+)$/;
function validatePrice(price: number, license: string) {
if (price < 0) throw new BadRequestError('priceCredits cannot be negative');
- if (price > 0 && license !== 'Cards-Pro-Only-1.0') {
- throw new BadRequestError('Paid decks must use the Cards-Pro-Only-1.0 license');
+ if (price > 0 && license !== 'Cardecky-Pro-Only-1.0') {
+ throw new BadRequestError('Paid decks must use the Cardecky-Pro-Only-1.0 license');
}
}
@@ -71,7 +71,7 @@ export class DeckService {
const validation = validateSlug(input.slug);
if (!validation.ok) throw new BadRequestError(`Slug invalid: ${validation.reason}`);
- const license = input.license ?? 'Cards-Personal-Use-1.0';
+ const license = input.license ?? 'Cardecky-Personal-Use-1.0';
const priceCredits = input.priceCredits ?? 0;
validatePrice(priceCredits, license);
diff --git a/services/cards-server/src/services/pull-requests.ts b/services/cards-server/src/services/pull-requests.ts
index 327093191..97f679cd3 100644
--- a/services/cards-server/src/services/pull-requests.ts
+++ b/services/cards-server/src/services/pull-requests.ts
@@ -103,7 +103,7 @@ export class PullRequestService {
}
private deckUrl(slug: string): string {
- const base = process.env.CARDS_WEB_URL || 'https://cards.mana.how';
+ const base = process.env.CARDS_WEB_URL || 'https://cardecky.mana.how';
return `${base}/d/${slug}`;
}
diff --git a/services/mana-auth/src/auth/sso-origins.ts b/services/mana-auth/src/auth/sso-origins.ts
index b732c92f9..528d392b9 100644
--- a/services/mana-auth/src/auth/sso-origins.ts
+++ b/services/mana-auth/src/auth/sso-origins.ts
@@ -25,8 +25,8 @@ export const PRODUCTION_TRUSTED_ORIGINS = [
'https://auth.mana.how',
// Separate apps (not part of the unified app)
'https://whopxl.mana.how', // Games
- 'https://cards.mana.how', // Cards spaced-repetition spinoff (own SvelteKit container, not the unified app)
- 'https://cards-api.mana.how', // Cards marketplace + community backend (cards-server)
+ 'https://cardecky.mana.how', // Cardecky spaced-repetition spinoff (own SvelteKit container, not the unified app)
+ 'https://cardecky-api.mana.how', // Cardecky marketplace + community backend (cards-server)
'https://memoro-app.mana.how', // Memoro web SPA (separate deploy under mana e.V.)
] as const;