# Marketplace-Restore — Playbook > **Status:** Plan, R0+R1 in Arbeit. Stand 2026-05-09. > **Vorgänger:** das alte `services/cards-server/` aus `managarten/` (mana- > monorepo) wurde am 2026-05-08 zusammen mit `apps/cards/` dekommissioniert, > weil beides eine Kopplung war — siehe Decommission-Commits > `bc158cb0b` (cards-server), `9cd871749` (apps/cards), `dd1bab09d` > (cards-core). Rollback-Tag: `cards-decommission-base` im managarten-Repo. > > Dieser Plan dokumentiert den **Restore** des Marketplace-Backends in > die Standalone-Cards-App, additiv zur bestehenden Greenfield-API. ## TL;DR - **Strategie-B-Klarstellung:** „Kein Code aus mana-monorepo" galt der Study-/FSRS-/Sync-Schicht (Dexie raus, server-authoritative). Marketplace war nie davon betroffen — er wurde nur mit-rausgerissen, weil er an `apps/cards` gekoppelt war. - **Restore vs. Neubau:** Restore. ~13.000 Zeilen reifer Code, 8 Phasen in Produktion gelaufen, Plan-Doku in Goldstandard-Qualität (654 Zeilen), bekannte Limitierungen sauber dokumentiert. Neubau wäre 4–6 Wochen, Restore ~14 Tage. - **Schema-Naming-Entscheidung:** **Eigenes `marketplace`-pgSchema** in derselben `cards`-DB. Begründung: saubere Read/Write-Trennung, Backup-Granularität (`pg_dump --schema=marketplace`), RLS-Policies pro Schema möglich, keine Kollisionen mit den existierenden `cards.{decks,cards,reviews,…}`-Tabellen. - **Service-Topologie:** Single-Service. Marketplace-Routen kommen unter `/api/v1/marketplace/*` in den bestehenden `cards/apps/api`. Kein zweiter Hono-Prozess, kein zweiter Container. YAGNI bei deinem Volumen. - **Frontend:** alte Routes 1:1 nach `cards/apps/web/src/routes/` — `/explore`, `/d/[slug]`, `/u/[slug]`, `/me/{published,subscribed,forks,purchases}`, `/admin/reports`. Imports auf Verdaccio-Pakete umstellen, Theming- Bridge-Aliase greifen automatisch. - **Restore-Reihenfolge:** R0 (Doku) → R1 (Schema) → R2 (Auth + Routes α/β) → R3 (γ + δ) → R4 (ε) → R5 (Frontend) → R6 (Smoke + erste Cardecky-Decks publishen). - **Was nicht im ersten Wurf:** Paid Decks (ζ.1) und Moderation-UI (η.1). Schema-Tabellen kommen mit, aber Code-Pfade bleiben dormant bis nach erstem Live-Test. --- ## Was die alte Implementation konnte (Phase α–η.1) Inhalt der archivierten Dokumente — **Original-Wahrheit** unter `cards/docs/marketplace/archive/`: | Datei | Was drin | |---|---| | `MARKETPLACE_PLAN_2026-05-07.md` | 654-Zeilen-Vollvision: Datenmodell, mana-credits-Flow, Cold-Start-Strategie, Anti-Patterns, Phasen-Status | | `COMPETITORS_2026-05.md` | 353-Zeilen-Konkurrenz-Analyse: Quizlet, AnkiHub, Brainscape, AnkiPro, AnkiApp, RemNote, Mnemosyne | | `GUIDELINES.md` | 367 Zeilen Community-Guidelines + Lizenz-Modell (SPDX + Cardecky-Personal-Use-1.0 + Cardecky-Pro-Only-1.0) | | `cards-server_CLAUDE.md` | 110 Zeilen Tech-Stack-Doku des Original-Service | Phasen-Status zum Zeitpunkt der Decommission (2026-05-08): | Phase | Status | Was lief produktiv | |---|---|---| | α — Skelett | ✅ live | 17-Tabellen-Schema, JWT-Auth, Container, Tunnel `cardecky-api.mana.how` | | β — Author-Workflow | ✅ live | Profil-Claim, Publish, Lizenz-Picker (SPDX), Preis-Eingabe, AI-Mod-First-Pass | | γ — Discovery | ✅ live | `/explore`, Stars, Follows, Author-Profile, Trending, Search (ILIKE) | | δ — Subscribe + Smart-Merge | ✅ live | Pull, Diff-View „+N · ~N · −N", FSRS-State erhalten über Karten-Hash-Diff | | ε — PRs + Discussions | ✅ live | „✏️ Verbessern" auf jeder Karte, Author-Merge, Inline-Threads, Notify-Mails | | ζ.1 — Paid Decks | ✅ live | 4-Schritt-Reserve→Purchase→Commit→Grant, 80/20-Split (90/10 für `verified_mana`) | | η.1 — Moderation | ✅ live | Reports, Admin-Inbox, Takedown-Workflow (kaskadiert auf PRs), Author-Ban | Bekannte Limitierungen (siehe `MARKETPLACE_PLAN_2026-05-07.md` §13a): PR-Merge-stale-blind, Reconciler-Lücke bei Paid-Pipeline, Mention-System fehlt, Discussion-Threading 1-Level, kein Refund-Self-Service. Alles dokumentiert, nichts unbekannt. --- ## Architektur-Anpassungen für den Restore ### 1. DB-Topologie: eigenes `marketplace`-pgSchema **Alt** (in `mana_platform`-DB, geteilt mit allen mana-Services): ``` mana_platform.cards.{authors, decks, deck_versions, …} — 17 Tabellen ``` **Neu** (in standalone `cards`-DB des Standalone-Repos): ``` cards.cards.{decks, cards, reviews, media_files, tags, imports} — Greenfield, bleibt cards.marketplace.{authors, decks, deck_versions, deck_cards, …} — Restore, neu ``` Begründung für ein **eigenes pgSchema** statt Tabellen-Prefix (`published_decks`, `published_deck_versions`, …): - **Sauberer Read-Path.** Public-Endpoints sehen nur das `marketplace`- Schema. Greenfield-Code (private Decks/Karten/Reviews) sieht ausschließlich `cards`. Kein Risiko, dass ein `SELECT * FROM decks` versehentlich beide Welten mischt. - **Backup-Granularität.** `pg_dump --schema=marketplace` exportiert nur den Marketplace-Stand für Compliance/Recovery. Privater Lern- Stand der User bleibt unangetastet. - **RLS-Policies pro Schema** — falls wir je Row-Level-Security einführen für public-decks-take-down-Workflows, ist das pro Schema konfigurierbar. - **Drizzle-kit-Push-Disziplin.** `schemaFilter: ['cards', 'marketplace']` hält beide Pushes sauber. Schema-Drift fängt sich auf Schema-Ebene. Drizzle-Variablennamen halten den `public`-Prefix aus dem alten Code: `publicDecks`, `publicDeckVersions`, `publicDeckCards`. So bleibt das intent klar, und Imports kollidieren nicht mit `cards.decks` aus dem Greenfield. ### 2. Auth-Modell: identisch, kein Refactor Alter cards-server: JWKS-Cache gegen `mana-auth`. Greenfield-cards-api: JWKS-Cache gegen `mana-auth`. Identisch. Service-Key-Auth (`X-Service-Key`) für Mana-Webhooks ebenfalls 1:1 übernommen. Optional-Auth-Middleware für Public-Endpoints (`/explore`, `/d/:slug`) muss aus dem alten Code mitkommen — der erlaubt anonymen Read und gibt zugleich personalisierte Daten (z.B. „Bist du Subscriber?") wenn ein Bearer mit-übermittelt ist. ### 3. Service-Topologie: Single-Service Alle Marketplace-Routen unter `/api/v1/marketplace/*` in `cards/apps/api/src/routes/marketplace/`: ``` cards/apps/api/src/routes/marketplace/ ├── authors.ts ├── decks.ts (publish, list, version reads) ├── engagement.ts (stars, subscribe, fork) ├── discussions.ts (card-discussions threads) ├── pull-requests.ts ├── moderation.ts (reports + admin) ├── purchases.ts (paid decks — dormant in R3, aktiv ab späterer Welle) └── explore.ts (discovery + search) ``` Hauptserver-Mount in `cards/apps/api/src/index.ts`: ```ts app.route('/api/v1/marketplace/authors', authorsRouter()) app.route('/api/v1/marketplace/decks', decksRouter()) // … ``` Vorteile: ein Prozess, ein Container, ein Tunnel-Endpoint (`cardecky-api.mana.how`), eine JWT-Validierung, eine Drizzle-DB-Connection. ### 4. Frontend: additive Routes, gleicher Stack `cards/apps/web/` ist SvelteKit + Svelte 5 (runes-only). Alter `apps/cards/apps/web/` war es auch. Routen werden 1:1 übernommen, mit drei Anpassungen: - **Imports:** `@mana/shared-*` kommt heute aus Verdaccio (npm.mana.how). `pnpm add @mana/shared-ui@^0.1.1 @mana/shared-share-protocol …`. - **Theming:** alte Components nutzten alte Token. Greenfield-Bridge- Aliase in `app.css` mappen die meisten alten Token aufs 12er-Mana- Vokabular. Test im Browser, ggf. Anpassungen. - **Auth-Hook:** `dev-stub.svelte.ts` bleibt für Phase-2-Lücke. Sobald echte mana-auth-Login-Flow ausgerollt ist (siehe `STATUS.md`), weicht der Stub für `@mana/shared-auth`. ### 5. Hash-Implementierung: bestehende `@cards/domain` benutzen Alter `cards-server/src/lib/hash.ts`: eigenständige SHA-256-Implementierung. Greenfield: `cardContentHash` in `@cards/domain` (Web-Crypto, deterministisch). Beim Restore: `lib/hash.ts` **nicht** mit-übernehmen, sondern Marketplace-Code auf `cardContentHash` aus `@cards/domain` umbiegen. Eine Hash-Definition für die ganze App. ### 6. mana-Service-Calls: identische Pattern Alter cards-server rief mana-credits, mana-llm, mana-media, mana-notify über `MANA_*_URL`-env-Vars. Greenfield-cards-api macht das genauso. Code 1:1 übernehmen. --- ## Wellen-Plan | Welle | Zustand | Was passiert | Blocker | |---|---|---|---| | **R0** | 🟡 in Arbeit | Doku-Restore: Archive aus cards-decommission-base + dieser Plan + Strategie-B-Klarstellung | — | | **R1** | ⏸ pending | Schema-Restore: 7 Schema-Files in `cards/apps/api/src/db/schema/marketplace/`, drizzle-kit push grün gegen lokale `cards`-DB | R0 | | **R2** | ⏸ pending | Backend Phase α + β: Author-Profile + Publish + AI-Mod-Stub | R1 | | **R3** | ⏸ pending | Backend Phase γ + δ: Discovery + Subscribe + Smart-Merge | R2 | | **R4** | ⏸ pending | Backend Phase ε: Pull-Requests + Card-Discussions | R3 | | **R5** | ⏸ pending | Frontend-Routes: `/explore`, `/d/[slug]`, `/u/[slug]`, `/me/{published,subscribed,forks}` | R4 | | **R6** | ⏸ pending | E2E-Smoke: erstes Cardecky-Deck publishen, von Till's Account subscriben, Smart-Merge testen | R5 | **Aufwand-Schätzung gesamt: ~14 Tage Real-Arbeit.** Bewusst aus dem ersten Restore-Wurf rausgelassen: - **ζ.1 Paid Decks** — Schema-Tabellen kommen in R1 mit, aber Routes/UI bleiben dormant. Re-Aktivierung als eigene Welle nach Live-Validation. Begründung: mana-credits-Integration ist heikel (4-step-Pipeline mit reservation-commit-grant), Author-Erlöse sind ein Verein-Compliance- Thema (Steuern, AGB, Refund-Policy), und solange Cardecky synthetic Decks publisht, gibt's keinen Need. - **η.1 Moderation-UI** — Schema-Tabellen + API-Endpoints kommen mit, Admin-Frontend (`/admin/reports`) wird ausgelassen bis erste echte User da sind. Take-Down via SQL für die ersten Wochen. - **θ Deep AI** (Auto-Tags, Embeddings, TTS) — bleibt explizit später- Phase, war im Original-Plan auch nicht für den ersten Wurf vorgesehen. --- ## Cardecky-Skill-Integration Der `/cards-deck`-Skill (siehe `~/.claude/skills/cards-deck/SKILL.md`) produziert Decks unter dem Cardecky-Plattform-User. Beim Marketplace- Restore wird Cardecky **automatisch zum Marketplace-Author**: 1. Bei R2 wird ein Init-Skript einen `marketplace.authors`-Row für Cardecky-User-ID anlegen (`slug='cardecky'`, `display_name='Cardecky'`, `pseudonym=false`, `verified_mana=true` — der Verein vergibt das Badge an seinen eigenen KI-Author). 2. Skill-Stufe 5 (Publish) wird erweitert um einen optionalen Schritt: nach Anlegen des privaten Decks kann der Skill ein `POST /api/v1/marketplace/decks/:id/publish` mit `semver=1.0.0` und einem auto-generierten Changelog hinterher schicken — dann ist das Deck sofort im `/explore` sichtbar. 3. Default bleibt aber „nur privat anlegen", weil: - das Validate-Stage (Stufe 4) eine menschliche Sichtung verdient, bevor 30 Karten öffentlich werden; - Reviewer-Stops nach Stufe 3 sind zwingend. Der Skill braucht keine Architektur-Änderung — er addiert nur einen optionalen 6. Schritt. --- ## Lizenz-Modell (aus dem Original übernommen) Aus `MARKETPLACE_PLAN_2026-05-07.md` §3 + `GUIDELINES.md`: | SPDX-ID | Erlaubt | Wann | |---|---|---| | `CC0-1.0` | alles, kein Attribution-Pflicht | für Public-Domain-fähige Karten | | `CC-BY-4.0` | alles, mit Attribution | meist gewählt für Wissens-Decks | | `CC-BY-SA-4.0` | alles, ShareAlike + Attribution | für Wikipedia-derivierte Decks | | `Cardecky-Personal-Use-1.0` | nur persönlicher Lern-Use, kein Re-Publish | **Default für kostenlose Decks** | | `Cardecky-Pro-Only-1.0` | nur via Kauf, kein Re-Publish, kein Fork | **Pflicht für paid Decks** (DB-CHECK enforced) | Der DB-CHECK auf `decks.price_credits = 0 OR license = 'Cardecky-Pro-Only-1.0'` ist im Schema beibehalten — Code-Bug kann nicht stillschweigend ein Paid-Deck mit CC-Lizenz publishen. --- ## Cold-Start-Strategie (aus dem Original) Kommt im ersten Restore-Wurf nicht aktiv zum Tragen, aber der Original- Plan §9 hat drei Hebel definiert, die bei Re-Launch greifen: 1. **Verein-Seed-Decks** — 50 hochwertige Cardecky-published Decks (Sprachen, Geschichte, Allgemeinwissen, Programmierung). Der `/cards-deck`-Skill ist genau das Werkzeug dafür. 2. **Anki-Top-100-Import-Service** — populäre CC-BY-Anki-Decks mit Original-Author-Attribution importieren, Original-Author bekommt `verified_mana`-Badge bei Registrierung. 3. **Influencer-Outreach** — 10–20 Anki-Power-Authoren (AnKing & Konsorten) gezielt ansprechen, sehr Author-freundlicher Cut. Hebel 2 + 3 sind Wachstumsmaßnahmen, nicht Phase-1. Hebel 1 ist sofort umsetzbar mit dem bestehenden Skill. --- ## Anti-Patterns aus dem Original-Plan §13 (gelten weiter) - **Kein 1-5-Sterne-Rating-System.** Stars (Bookmark) ja, Bewertungen nein. - **Kein Reddit-Style-Voting** auf Karten/PRs/Discussions. Hacker-News-Effekt. - **Kein „Karten der Woche" allein-algorithmisch.** Editorial + Trending- Liste, aber niemals reiner Algo-Feed. - **Kein Anki-Bashing im Marketing.** Bridge nicht Burning. - **Keine Pflicht-Klarnamen.** Pseudonyme bleiben gleichberechtigt. - **Kein Marketplace-Cut über 30 %.** Standard 80/20, verified 90/10. --- ## Offene Punkte - **PR-Merge-stale-blind aus dem Original.** Bekannte Limitierung: wenn Author zwischen PR-Open und Merge selbst eine Karte ändert, deren `previousContentHash` der PR matched, gewinnt stumm der PR. Im ersten Restore-Wurf so übernehmen wie war; späterer Fix via optimistic locking auf `baseVersionId` der PR-Row mit Reject bei Mismatch. - **Reconciler-Cron für Paid-Pipeline-Inkonsistenzen.** Original §13a beschreibt das Loch: bei Commit-/Grant-Failure nach Schritt 2 bleibt eine Purchase-Row mit `creditsTransaction = null`. Beim Restore initial nicht aktiv (Paid-Decks dormant), aber sobald Phase ζ reaktiviert wird, ist Reconciler ein muss-haben. - **`cards-decommission-base`-Tag im managarten-Repo.** Falls jemand managarten löscht oder das Tag nochmal entfernt, geht der Restore- Pfad verloren. Empfehlung: Schema-Files + ausgewählte Code-Snippets einmal nach `cards/docs/marketplace/archive/` kopieren (Doks sind schon drin, Code-Files folgen optional bei R1+R2). - **Schema-Migrations-Pfad bei späterer Drizzle-Version.** Greenfield- Cards plant Migration auf Drizzle 0.45/zod-4 mit der Plattform mit (`mana/docs/MIGRATION_DRIZZLE_ZOD.md`). Marketplace-Schema kommt mit Drizzle 0.38 — sollte mit upgradeen, idealerweise atomar. - **Karten-Hash-Konsistenz zwischen Greenfield und Marketplace.** Greenfield-`@cards/domain` `cardContentHash` ist die SoT. Marketplace- `deck_cards.content_hash` muss mit demselben Algorithmus berechnet werden — sonst funktioniert Smart-Merge nicht. Beim R2-Port-Pass testen, dass `cardContentHash({type,fields})` aus `@cards/domain` byte-identisch ist mit dem alten `cards-server/src/lib/hash.ts`. Wenn nicht: alten Code anpassen, nicht `@cards/domain` brechen.