R0 (Doku): - Archiv unter docs/marketplace/archive/ aus managarten-Tag cards-decommission-base: MARKETPLACE_PLAN (654 Z., Vollvision mit mana-credits-Flow, Anti-Patterns), COMPETITORS, GUIDELINES, cards-server_CLAUDE. - docs/playbooks/MARKETPLACE_RESTORE.md mit Schema-Naming-Entscheidung (eigenes marketplace-pgSchema), Wellen R0-R6, Cardecky-Skill- Integration, Lizenz-Modell. - CLAUDE.md Invariante 2: Strategie-B gilt nur für Study-/FSRS-/Sync- Schicht; Marketplace-Restore ist explizite Ausnahme. - STATUS.md: Phase 12 R0+R1 durch. R1 (Schema): - 16 Tabellen + 5 Enums im neuen marketplace-pgSchema (authors, decks, deck_versions, deck_cards, tag_definitions, deck_tags, deck_stars, deck_subscriptions, deck_forks, deck_pull_requests, card_discussions, deck_reports, ai_moderation_log, deck_purchases, author_payouts, author_follows). - drizzle.config.ts: schemaFilter ['cards', 'marketplace']. - Greenfield cards-pgSchema unangetastet. - DB-CHECK decks_price_requires_license verifiziert (paid Deck mit CC-BY wirft sauber ab). - type-check + 56 API-Tests grün, drizzle-kit push idempotent. Decks dormant (kein Code-Pfad ruft die Tabellen). R2 (Backend α/β: Author-Profile + Publish + AI-Mod) als nächstes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
367 lines
15 KiB
Markdown
367 lines
15 KiB
Markdown
# 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:** 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
|
||
|
||
Die schönste, einfachste Karteikarten-App mit Spaced Repetition —
|
||
zuerst nur Web, später Mobile, KI-Generierung als Phase 2.
|
||
|
||
## 2. Game-Dev-Prinzip: zuerst nur der Core Gameloop
|
||
|
||
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 Cardecky
|
||
|
||
```
|
||
Start
|
||
│
|
||
▼
|
||
"Du hast N Karten heute fällig" ─────► (wenn 0: "Alles gelernt — komm später wieder")
|
||
│
|
||
▼
|
||
[Lernen starten]
|
||
│
|
||
▼
|
||
Vorderseite zeigen ──► User denkt ──► Tap/Space ──► Rückseite zeigen
|
||
│
|
||
▼
|
||
Selbst-Bewertung: 1=nochmal · 2=schwer · 3=gut · 4=leicht
|
||
│
|
||
▼
|
||
FSRS rechnet next-due ──► nächste Karte (oder Session-Ende)
|
||
│
|
||
▼
|
||
Session-Ende: "X Karten gelernt, nächste in Y Stunden"
|
||
│
|
||
└─► zurück zum Start
|
||
```
|
||
|
||
Sekundäre Loops (Karten erstellen, Decks verwalten) werden gebaut, sind
|
||
aber UI-arm. **Tertiäre Loops (KI-Generierung, Voice, Sharing) sind
|
||
Phase 2 und werden in Phase 1 nicht angefasst.**
|
||
|
||
### Was Phase 1 enthält
|
||
|
||
- Decks anlegen / löschen / umbenennen
|
||
- Karten manuell erstellen (Markdown-Inhalt)
|
||
- **Kartentypen:** Basic, Basic + Reverse, Cloze, Type-In (siehe §6)
|
||
- Lernsession mit FSRS v6, **inklusive per-User-Parameter-Tuning**
|
||
- "Heute fällig"-Übersicht + Streak-Zähler
|
||
- Tags auf Decks (das Modul hat sie ohnehin schon, raus wäre Mehrarbeit)
|
||
- PWA-installierbar, offline-fähig
|
||
- Auth via mana-auth, Sync via mana-sync
|
||
|
||
### Was Phase 1 absichtlich NICHT enthält
|
||
|
||
- KI-Generierung von Karten (kein PDF-Upload, keine Bild→Karte)
|
||
- Voice/TTS-Lernen
|
||
- Anki-Import / Export
|
||
- Statistik-Dashboards (nur Streak + Tagessumme)
|
||
- Public Decks / Marktplatz / Sharing
|
||
- Stripe / Bezahlung
|
||
- Mobile-App (PWA-tauglich aber kein Expo)
|
||
- Eigene Domain & Marketing-Landing
|
||
- Mehrsprachigkeit über Deutsch hinaus
|
||
- Bilder / Audio in Karten
|
||
- Image-Occlusion-Karten, Audio-Karten, Multiple-Choice
|
||
- Custom Card-Templates / WYSIWYG-Editor
|
||
- Erweiterte Suche
|
||
|
||
Jede dieser Features ist legitim — aber nur, wenn der Loop steht.
|
||
|
||
## 3. Goldene Regeln
|
||
|
||
1. **Simpel schlägt vollständig.** Wenn ein Feature nicht zum Core Gameloop gehört, kommt es in einen Phase-2-Backlog, nicht in den Code.
|
||
2. **Open Source only.** Jede Library, jedes Tool, jeder Dienst muss eine OSI-konforme Lizenz haben (MIT, Apache 2.0, BSD, MPL, AGPL akzeptabel). Keine Closed-Source-SDKs, keine proprietären APIs als Pflichtabhängigkeit.
|
||
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. **`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.
|
||
|
||
## 4. Tech-Stack (Phase 1)
|
||
|
||
Alles bereits im Verein verwendet, alles OSI-Open-Source.
|
||
|
||
### Frontend
|
||
| Schicht | Wahl | Lizenz |
|
||
|---|---|---|
|
||
| Framework | SvelteKit 2 | MIT |
|
||
| UI-Sprache | Svelte 5 (Runes) | MIT |
|
||
| Sprache | TypeScript 5 | Apache-2.0 |
|
||
| Styling | Tailwind CSS 4 | MIT |
|
||
| Build/Dev | Vite | MIT |
|
||
| PWA | `@vite-pwa/sveltekit` (über `@mana/shared-pwa`) | MIT |
|
||
| Icons | über `@mana/shared-icons` | MIT |
|
||
| Markdown-Render | `marked` + `DOMPurify` | MIT |
|
||
|
||
### Datenhaltung (Client)
|
||
| Schicht | Wahl | Lizenz |
|
||
|---|---|---|
|
||
| Local Store | IndexedDB via Dexie | Apache-2.0 |
|
||
| Local-Store-Wrapper | `@mana/local-store` (intern) | — |
|
||
| Verschlüsselung | AES-GCM-256 via `@mana/shared-crypto` (Phase 2 — Hooks bereits an allen Schreib-/Lese-Pfaden, Wirkung deferred bis Vault-Server-Roundtrip steht; siehe `src/lib/data/crypto.ts`) | — |
|
||
|
||
### Spaced Repetition
|
||
| Schicht | Wahl | Lizenz |
|
||
|---|---|---|
|
||
| Algorithmus | FSRS v6 (Free Spaced Repetition Scheduler) | BSD-3 |
|
||
| TS-Implementation | `ts-fsrs` (offizielle Portierung, mit Optimizer) | MIT |
|
||
| Per-User-Tuning | `ts-fsrs`-Optimizer, läuft client-seitig nach ≥ 50 Reviews | MIT |
|
||
|
||
### Deployment
|
||
| Schicht | Wahl | Lizenz |
|
||
|---|---|---|
|
||
| Adapter | `@sveltejs/adapter-node` | MIT |
|
||
| Container | Docker, hinter Cloudflare Tunnel | Apache-2.0 |
|
||
| Host | Mac mini (siehe `docker-compose.macmini.yml`) | — |
|
||
|
||
### Tooling
|
||
| Schicht | Wahl | Lizenz |
|
||
|---|---|---|
|
||
| Paket-Manager | pnpm 9 | MIT |
|
||
| Monorepo-Orchestrierung | Turborepo (vorhanden) | MPL-2.0 |
|
||
| Linting | ESLint (`@mana/eslint-config`) | MIT |
|
||
| Formatierung | Prettier | MIT |
|
||
| Tests (Unit) | Vitest | MIT |
|
||
| Tests (E2E) | Playwright | Apache-2.0 |
|
||
| TS-Config | `@mana/test-config`, `@mana/shared-vite-config` | — |
|
||
|
||
### Backend in Phase 1: keiner
|
||
|
||
Phase 1 braucht **keinen eigenen Service**. Lese-/Schreibpfad geht
|
||
ausschließlich über IndexedDB → `mana-sync` (existiert) → Postgres.
|
||
|
||
Erst wenn KI-Generierung (Phase 2) dazukommt, entsteht
|
||
`services/cards-server` (Hono + Bun, analog zu allen anderen
|
||
Verein-Services).
|
||
|
||
## 5. Zentrale Mana-Bausteine (Pflicht in Phase 1)
|
||
|
||
### Services (laufen bereits, nur konsumieren)
|
||
| Service | Port | Wofür in Cardecky |
|
||
|---|---|---|
|
||
| `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). |
|
||
| `mana-events` | 3115 | Domain-Events für Streak-Logik. |
|
||
| `mana-notify` | 3040 | "Du hast X Karten fällig"-Push (Phase 1.5). |
|
||
| `mana-credits` | 3061 | **Erst Phase 2** (KI-Generierung). |
|
||
| `mana-subscriptions` | 3063 | **Erst Phase 2** (Pro-Tier). |
|
||
| `mana-llm`, `mana-stt`, `mana-tts` | – | **Erst Phase 2.** |
|
||
| `mana-media` | 3015 | **Erst wenn Bilder in Karten erlaubt sind.** |
|
||
|
||
### Workspace-Pakete (`@mana/*`)
|
||
| Paket | Wofür in Cardecky |
|
||
|---|---|
|
||
| `@mana/shared-auth` | Client-seitiger Auth-Hook (SSO-Flow, JWT-Handling). |
|
||
| `@mana/shared-auth-ui` | Login/Logout-Komponenten. |
|
||
| `@mana/shared-hono` | (sobald cards-server existiert) Auth-/Health-/Error-Middleware. |
|
||
| `@mana/shared-branding` | App-Registry-Eintrag (Tier=`public`, Branding, Subdomain). |
|
||
| `@mana/shared-types` | Geteilte TS-Typen. |
|
||
| `@mana/shared-utils` | Utility-Funktionen. |
|
||
| `@mana/shared-ui` | UI-Komponenten. |
|
||
| `@mana/shared-theme`, `@mana/shared-theme-ui` | Theme-Tokens, Dark/Light. |
|
||
| `@mana/shared-tailwind` | Tailwind-Preset. |
|
||
| `@mana/shared-i18n` | Übersetzungsfundament (Phase 1: nur DE registriert). |
|
||
| `@mana/shared-icons` | Icon-Set. |
|
||
| `@mana/shared-privacy` | Visibility-Enum für Decks (Sharing erst Phase 2, aber Feld vorbereitet). |
|
||
| `@mana/shared-crypto` | AES-GCM-256 für sensible Felder. |
|
||
| `@mana/shared-pwa` | Manifest, Service-Worker, Install-Prompt. |
|
||
| `@mana/shared-vite-config` | Vite-Defaults. |
|
||
| `@mana/shared-error-tracking` | Error-Reporting. |
|
||
| `@mana/shared-logger` | Strukturiertes Logging (Server-Seite, sobald relevant). |
|
||
| `@mana/shared-stores` | Geteilte Local-Store-Helpers. |
|
||
| `@mana/shared-tags` | Tags auf Decks. |
|
||
| `@mana/local-store` | Dexie-Setup, Sync-Hooks. |
|
||
| `@mana/eslint-config` | Lint-Regeln. |
|
||
| `@mana/test-config` | Vitest-Defaults. |
|
||
| `@mana/feedback` | In-App-Feedback-Widget. |
|
||
| `@mana/help` | Hilfe-Overlay. |
|
||
|
||
**Erst Phase 2 oder später:** `@mana/shared-llm`, `@mana/shared-ai`,
|
||
`@mana/local-llm`, `@mana/local-stt`, `@mana/credits`, `@mana/qr-export`,
|
||
`@mana/wallpaper-generator`, `@mana/website-blocks`,
|
||
`@mana/shared-research`, `@mana/shared-uload`, `@mana/shared-storage`.
|
||
|
||
### Datenpfad
|
||
|
||
Cardecky übernimmt 1:1 das Mana-Datenpfad-Pattern:
|
||
|
||
```
|
||
User-Aktion → Store → encryptRecord → Dexie → Hooks (_pendingChanges)
|
||
→ mana-sync → Postgres (mana_platform.cards.*) → andere Clients
|
||
```
|
||
|
||
appId = `cards`. Tabellen: `cardDecks`, `cards`, `cardReviews`,
|
||
`cardStudyBlocks`, `deckTags`.
|
||
|
||
## 6. Datenmodell — erweiterbar gedacht
|
||
|
||
Heutiges Modul kennt nur `front`/`back`. Damit weitere Kartentypen
|
||
ohne Schema-Bruch dazukommen, wechseln wir auf ein **Felder-Map +
|
||
Typ-Diskriminator**:
|
||
|
||
```ts
|
||
type CardType =
|
||
| 'basic' // Phase 1: front/back
|
||
| 'basic-reverse' // Phase 1: erzeugt zwei Lernrichtungen aus einer Karte
|
||
| 'cloze' // Phase 1: Lückentext, eine Subkarte pro Cluster
|
||
| 'type-in' // Phase 1: User tippt Antwort, exact-match-Vergleich
|
||
| 'image-occlusion' // Phase 2
|
||
| 'audio' // Phase 2
|
||
| 'multiple-choice' // ggf. Phase 2
|
||
|
||
interface LocalCard extends BaseRecord {
|
||
deckId: string
|
||
type: CardType
|
||
fields: Record<string, string> // basic: { front, back } · cloze: { text, extra? }
|
||
// FSRS-State liegt nicht hier, sondern in cardReviews (1:N pro Subkarte)
|
||
order: number
|
||
}
|
||
|
||
interface LocalCardReview extends BaseRecord {
|
||
cardId: string
|
||
subIndex: number // basic-reverse → 0|1, cloze → c1, c2, …
|
||
stability: number // FSRS
|
||
difficulty: number // FSRS
|
||
due: string // ISO
|
||
reps: number
|
||
lapses: number
|
||
state: 'new' | 'learning' | 'review' | 'relearning'
|
||
lastReview?: string
|
||
}
|
||
|
||
interface LocalCardStudyBlock extends BaseRecord {
|
||
date: string // YYYY-MM-DD
|
||
cardsReviewed: number
|
||
durationMs: number
|
||
}
|
||
```
|
||
|
||
**Cloze-Syntax:** Anki-kompatibel: `{{c1::Wort}}`, `{{c1::Wort::Hinweis}}`.
|
||
Eine Cloze-Karte mit Cluster `c1`+`c2` erzeugt 2 Reviews
|
||
(`subIndex 1`, `subIndex 2`).
|
||
|
||
**Markdown:** `marked` + `DOMPurify` rendern Front/Back. Cloze-Tags
|
||
werden vor dem Markdown-Parser zu HTML-Spans umgewandelt, damit sie im
|
||
Render erhalten bleiben.
|
||
|
||
**Migration aus dem Bestand:** existierende `front`/`back`-Karten werden
|
||
beim ersten Schema-Upgrade auf `type='basic'` mit
|
||
`fields={front, back}` migriert. Alte Spalten bleiben für eine
|
||
Übergangsversion lesbar (siehe `docs/DATABASE_MIGRATIONS.md`).
|
||
|
||
## 7. Daten-Contract mit dem mana-Modul
|
||
|
||
Wichtig: das **bestehende `cards`-Modul in der Mana-Web-App bleibt
|
||
erhalten**. Cardecky und das mana-Modul schreiben in dieselben
|
||
Postgres-Tabellen.
|
||
|
||
Daher gilt:
|
||
- Schema-Änderungen werden **gemeinsam** im mana-Modul und im
|
||
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`.
|
||
|
||
**Reihenfolge:** Phase 0 (mana-Modul um neue Tabellen + Kartentyp-Felder
|
||
+ FSRS erweitern) wird **vor** dem Standalone-Build durchgezogen. So
|
||
gibt es nie zwei Wahrheiten zur Datenstruktur.
|
||
|
||
## 8. Definition of Done für Phase 1
|
||
|
||
Phase 1 ist fertig, wenn:
|
||
|
||
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),
|
||
- eine Lernsession starten und mit FSRS-Bewertung durchspielen,
|
||
- die App schließen und am nächsten Tag die richtigen fälligen Karten wiederfinden.
|
||
2. FSRS-Per-User-Tuning läuft automatisch nach ≥ 50 Reviews und überschreibt die Default-Parameter.
|
||
3. Die App ist als PWA installierbar und offline-bedienbar (Karten lernen ohne Netz).
|
||
4. Auth läuft komplett über mana-auth (kein Eigen-Login).
|
||
5. Daten landen in Postgres und sind im bestehenden mana-Modul sichtbar (gleiche Datenquelle, kein Drift).
|
||
6. `pnpm validate:all` grün.
|
||
7. Mindestens drei Smoke-E2E-Tests (Playwright):
|
||
- „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 (`cardecky.mana.how`).
|
||
|
||
Alles andere ist Phase 2.
|
||
|
||
## 9. Repo-Struktur (Phase 1)
|
||
|
||
```
|
||
apps/cards/
|
||
├── apps/
|
||
│ └── web/ # SvelteKit-App, einziges Surface in Phase 1
|
||
│ ├── src/
|
||
│ │ ├── lib/
|
||
│ │ │ ├── data/ # Dexie + Sync-Anbindung
|
||
│ │ │ ├── fsrs/ # ts-fsrs-Wrapper + Optimizer-Hook
|
||
│ │ │ ├── cards/ # Kartentyp-Renderer (basic, cloze, type-in)
|
||
│ │ │ ├── stores/ # Decks, Cards, Reviews, StudyBlocks
|
||
│ │ │ └── ui/ # Komponenten (DeckList, CardEditor, Session)
|
||
│ │ └── routes/
|
||
│ │ ├── +layout.svelte
|
||
│ │ ├── +page.svelte # Heute fällig + Decks
|
||
│ │ ├── decks/[id]/+page.svelte # Deck-Detail + Karten
|
||
│ │ └── learn/[deckId]/+page.svelte # Lernsession
|
||
│ ├── package.json
|
||
│ ├── svelte.config.js
|
||
│ └── vite.config.ts
|
||
├── GUIDELINES.md # ← dieses Dokument
|
||
└── README.md
|
||
```
|
||
|
||
`apps/cards/apps/mobile/` und `apps/cards/apps/landing/` sind erst
|
||
Phase 2/3.
|
||
|
||
## 10. PR-Checkliste
|
||
|
||
Bei jedem Pull-Request gefragt:
|
||
|
||
- Gehört die Änderung zum Core Gameloop?
|
||
- Wenn nein: rechtfertigt sie sich aus einer Pflicht (Auth, Sync, Build)?
|
||
- Wird ein bestehendes `@mana/*` Paket genutzt statt neu zu bauen?
|
||
- Ist jede neue Dependency Open-Source und im Verein bereits in Verwendung?
|
||
- Sind Datenmodell-Änderungen mit dem mana-Modul konsistent?
|
||
- Bricht die Änderung das Versprechen "Erweiterbare Daten, simples UI"?
|
||
|
||
## 11. Analytics-Events (Mindestumfang Phase 1)
|
||
|
||
Über `mana-analytics`:
|
||
|
||
- `cards_session_started` — `{ deckId, dueCount }`
|
||
- `cards_card_rated` — `{ cardId, type, grade (1–4), elapsedMs }`
|
||
- `cards_session_completed` — `{ deckId, cardCount, durationMs }`
|
||
- `cards_deck_created` — `{ deckId }`
|
||
- `cards_card_created` — `{ deckId, type }`
|
||
- `cards_fsrs_optimized` — `{ reviewCount, paramsHash }`
|
||
- `cards_pwa_installed` — Standard-PWA-Event
|
||
|
||
Reicht für die Core-Loop-Validierung. Mehr Events erst, wenn eine
|
||
konkrete Frage entsteht, die Daten beantworten sollen.
|
||
|
||
## 12. Hinweis im mana-Modul
|
||
|
||
Sobald `cardecky.mana.how` live ist, bekommt das mana-Modul einen
|
||
**dezenten** Hinweis (z.B. ein Banner oder Badge über der ListView):
|
||
"Cardecky gibt es jetzt auch als eigenständige App". Kein Pop-up, kein
|
||
forcierter Redirect — User entscheiden selbst.
|