# Lessons aus mana-monorepo für Cards-Greenfield **Stand:** 2026-05-08, Read-Day-Output **Quelle:** `mana-monorepo/{packages/cards-core,apps/mana/.../modules/cards,services/cards-server}` **Verwendung:** Architektonische Lehren für den Greenfield-Build. Kein Code wird übernommen, nur Designs. ## TL;DR - **Domain-Library separieren** (`@cards/domain`, framework-agnostic) - **Card-Type als discriminated union** mit `fields: Record` + `subIndex`-Granularität - **FSRS via `ts-fsrs` v5.3.2** in dünnem Adapter, Reviews bleiben PLAINTEXT - **Markdown-Editor**, kein Rich-Text/WYSIWYG - **Keyboard-driven Study-View** (Space=Reveal, 1–4=Grade) - **Marketplace ist separate Concern** (eigener Server in mana-monorepo, NICHT im Cards-MVP) - **Encryption-Registry zentral planen**, MVP `enabled: false` - **AI-Tools sind dünn** in mana-monorepo (nur Draft `create_card`); wir können sauber neu definieren --- ## 1. Domain-Library als eigener Workspace-Package mana-monorepo hat `packages/cards-core/` mit Pure-TS: - Card-Types, Cloze-Parser, FSRS-Wrapper, Markdown-Render-Helpers - Keine Dexie-, Sync-, oder UI-Abhängigkeiten - Konsumiert von App-UI-Modul UND von `services/cards-server` → **Bei uns:** `packages/cards-domain/` analog. Bewusst kein `@cards/core` (Name-Konflikt), sondern `@cards/domain`. Steht. ## 2. Card-Type-Modell mana-monorepo unterstützt: `'basic' | 'basic-reverse' | 'cloze' | 'type-in' | 'image-occlusion' | 'audio' | 'multiple-choice'`. Datenstruktur: - `fields: Record` als generischer Slot (statt `front`/`back`-Spalten) - Pro Type unterschiedliche Field-Sets: - `basic`/`basic-reverse`: `{ front, back }` - `cloze`: `{ text, extra? }` - `type-in`: `{ question, expected }` **SubIndex-Granularität:** Pro Karte gibt es N Reviews (mit `subIndex`): - `basic`: 1 Review (subIndex 0) - `basic-reverse`: 2 Reviews (front→back UND back→front) - `cloze`: 1 Review pro Cluster-Index (`{{c1::...}}`, `{{c2::...}}`) → **Bei uns:** MVP nur `basic` + `basic-reverse`. Aber `Card`-Schema gleich für die volle CardType-Future-Union vorgesehen, damit Schema-Migration klein bleibt. SubIndex-Pattern in Reviews-Tabelle übernehmen. ## 3. FSRS-Library + Adapter-Pattern - `ts-fsrs` v5.3.2 — dependency-free, Date-basierte API - mana-monorepo wraps in `LocalCardReview` ↔ `ts-fsrs.Card` Adapter (ISO-Strings ↔ Date) - Funktionen: `newReview()` (init), `gradeReview(review, rating)` (next state) → **Bei uns:** Genau gleicher Wrapper. Phase 3. **Wichtige Regel:** Reviews bleiben **PLAINTEXT** in der DB, weil der Scheduler täglich auf `due <= now` quert. Encryption müsste täglich N Reviews entschlüsseln — geht nicht. mana-monorepos `crypto/registry.ts` listet `cardReviews` explizit als plaintext-allowlisted. → **Bei uns:** Schema-Doku wird das erwähnen. MVP-Encryption-OFF macht das ohnehin moot, aber Future-Proofing wichtig. ## 4. Cloze-Parser: Token-basiert, nicht Regex mana-monorepo hat `packages/cards-core/src/cloze.ts` mit: - Anki-kompatible Syntax: `{{c1::answer}}` oder `{{c1::answer::hint}}` - `tokenize()` → `Array<{ kind: 'text' | 'cluster', ... }>` - `clusters()` gruppiert pro Cluster-Index - `renderCloze(source, hideIndex)` → `{ front, back, answer }` **Warum Token-basiert:** Ein Cluster kann mehrfach im Text vorkommen (`{{c1::Berlin}} … {{c1::Berlin}}`). Beide müssen synchron geblankt werden. Token-Parser macht das trivial; Regex-Replace nicht. → **Bei uns:** Cloze ist Phase 8+. Wenn implementiert: Token-basiert, Anki-kompatible Syntax. Library-Wahl: gleich wie ts-fsrs eigene mini-Library schreiben (klein), oder `@anki/cloze-parser` falls existent (TBD). ## 5. Local-First-Stack ist nicht trivial mana-monorepos Cards-Modul nutzt: - Dexie + 5 Tabellen (`cardDecks`, `cards`, `cardReviews`, `cardStudyBlocks`, `deckTags`) - mana-sync (Go) für Server-Sync mit Field-Level-LWW - `__fieldMeta` pro Record für Konflikt-Detection - `_updatedAtIndex` Shadow-Column für orderBy - Encryption-Layer mit Master-Key aus mana-auth **Lessons:** - Sync-Engine ist nicht trivial (siehe `apps/mana/.../data/DATA_LAYER_AUDIT.md`) - Quota-Recovery, Backoff, RLS, Encryption-Rollout sind ihre eigenen Features - "Schnell mal was Local-First bauen" ist eine Illusion → **Bei uns:** **Server-authoritative MVP, Local-First erst via mana-sync-Federation** (Variante III in CARDS_GREENFIELD.md). Damit überspringen wir das gesamte Komplexitäts-Paket. Wenn später Local-First nötig: Cards bekommt `appId=cards` in mana-sync, statt eigenen Stack zu bauen. ## 6. Encryption-Registry zentral mana-monorepo hat `apps/mana/.../data/crypto/registry.ts`: - Cards: `{ enabled: true, fields: ['front', 'back', 'fields'] }` (ein-Tabelle) - CardDecks: `{ enabled: true, fields: ['name', 'description'] }` - cardReviews + cardStudyBlocks: explizit plaintext (Performance-Gründe oben) Regel: Plaintext = IDs/Timestamps/Enums/Sortkeys/Indizes. Encrypt = User-Typed-Content. → **Bei uns:** Encryption initial **AUS** (CARDS_GREENFIELD.md). Wenn nachgerüstet: gleiche Aufteilung. Felder-Allowlist in `packages/cards-domain/src/crypto/registry.ts` zentral, von `apps/api/src/db/...` konsumiert. ## 7. Card-Editor: Markdown statt Rich-Text mana-monorepo nutzt: - Stateless `CardFace.svelte` (`card`, `subIndex`, `showBack`, Callbacks) - Render-Logik: - Basic: `renderMarkdown(card.fields.front/back)` - Cloze: `renderCloze(card.fields.text, subIndex)` + extra-hint - Type-In: Input-Feld + case-insensitive Vergleich gegen `expected` **Anti-Pattern (vermieden):** Rich-Text-Editor mit Toolbar/Undo/Bold-Hotkeys. → **Bei uns:** Phase 4: Markdown-Editor. Library: vermutlich `marked` + `DOMPurify`. CodeMirror oder Monaco wäre Overkill für Karten — wir brauchen kein Syntax-Highlighting. ## 8. Study-View: Keyboard-driven mana-monorepos `/cards/learn/[deckId]/+page.svelte`: - **Space/Enter:** Antwort aufdecken - **1/2/3/4:** Grade (Again/Hard/Good/Easy → ts-fsrs Rating-Enum) - **Queue snapshot am Session-Start** — verhindert Mid-Session-Verschiebung neu fälliger Karten - **Skip-Logic:** Wenn `INPUT` focused, ignoriere Hotkeys → **Bei uns:** Phase 4. Dasselbe Pattern. Hotkey-Handler-Helper ``-Component oder Effect mit `addEventListener`. ## 9. Marketplace: separater Service, NICHT im MVP mana-monorepo hat **`services/cards-server/`** (Hono+Bun, Port 3072 — Konflikt mit unserer mana-share-Plattform!) als Marketplace-Backend: - Tabellen: `decks` (public, slug-indexed), `deck_versions` (immutable snapshots), `deck_cards` (versionId, type, fields JSON, contentHash) - Smart-Merge via per-card SHA-256-Hashes: Subscriber pullen neue Versionen ohne FSRS-State zu verlieren - Routes: POST `/decks`, GET `/:slug`, POST `/:slug/publish` **Wichtig:** Das ist ein **eigenes Feature**, kein Teil des Cards-Frontend-Moduls. Cards-Web bringt Decks lokal, Marketplace ist read-only-Browse + Publish. → **Bei uns:** **Nicht im MVP.** Phase 12+. Wenn nachgerüstet: eigenes Backend (oder dedizierter Endpoint in cards-api), nicht in MVP-Schema. Smart-Merge-Pattern (content-hash) ist ein gutes Design — übernehmen, wenn es so weit ist. **Decommission-Konsequenz:** Wenn Cards-Greenfield live ist, muss `mana-monorepo/services/cards-server/` ebenfalls weg (siehe CARDS_GREENFIELD.md → Decommission). Steht. ## 10. AI-Tools sind dünn mana-monorepos `lib/modules/cards/tools.ts` hat: - 1 Tool: `create_card` (name, deckId, front, back) — als Draft, NICHT in `AI_TOOL_CATALOG` registriert - Keine `list_decks`, `get_deck_stats`, `update_card`-Tools → **Bei uns:** Phase 7 entscheidet Scope. Vermutlich `cards.create` + `cards.search` für MVP. Mehr Tools wenn Persona-Runner-Use-Cases erscheinen. ## 11. Routing-Pattern mana-monorepos `(app)/cards/`-Routen: - `/cards/decks` — ListView - `/cards/decks/[id]` — DetailView (EditName, EditDescription, EditColor, VisibilityPicker) - `/cards/learn/[deckId]` — Study-Session-View - `/cards/explore` — Marketplace-Browse - `/cards/progress` — Stats, Streak, Heatmap → **Bei uns:** Cards ist Standalone, nicht `(app)/cards/...`-Sub-Tree. Routing flacht aus zu `/decks`, `/decks/:id`, `/study/:deckId`. `/explore` und `/progress` sind Polish (Phase 9). ## 12. Visibility: einfacher Enum reicht mana-monorepo nutzt `@mana/shared-privacy` mit `VisibilityLevel = 'private' | 'space' | 'public'`. Bei Änderungen wird `visibilityChangedAt` + `visibilityChangedBy` getrackt. → **Bei uns:** Gleiches Enum als TypeScript-Type in `@cards/domain`. Audit-Felder optional ab Phase 9 (DSGVO-Polish). ## 13. Tests mit fake-indexeddb mana-monorepo nutzt `fake-indexeddb` für Unit-Tests gegen Dexie ohne echte DB. → **Bei uns:** **Nicht relevant** — wir sind server-authoritative, keine Dexie. Vitest + Hono `app.request()` (wie in mana-Plattform) für API-Tests, Playwright für e2e. ## 14. Domain-Events + Analytics früh mana-monorepo emittiert `CardCreated` u.ä. an einen domain-event-Bus + ruft `CardsEvents.cardCreated()` für Analytics. → **Bei uns:** Domain-Events gehen über mana-events (`card.created`, `card.studied`, `deck.completed`). Analytics ggf. später. Phase 5 macht die Event-Anbindung. ## 15. Hash-Everything-Early mana-monorepos Marketplace hat content-hash auf jeder Karte + jedem Deck-Version-Snapshot. Ermöglicht Smart-Merge ohne Diffing. → **Bei uns:** Hash-Spalte auf `cards` und `decks` schon im MVP-Schema einplanen, auch wenn Marketplace nicht da ist. Cheap to add, expensive to retrofit. --- ## Anti-Patterns aus mana-monorepo, die wir vermeiden - **Rich-Text-Editor mit Toolbar:** Markdown reicht für Karten-Inhalte - **Real-time-Collaboration:** Cards sind privat, async-Sync genügt - **Geteilte Dexie für 27 Module:** entfällt, Cards hat eigene Postgres - **`updatedAt` als synced data field:** mana-monorepo musste das nachträglich auf `__fieldMeta`-deriviert umbauen (siehe `mana-monorepo/CLAUDE.md` §"Conflict-Detection 2026-04-26"). **Wir machen es ab Tag 1 richtig:** `updatedAt` wird beim Read aus `max(__fieldMeta[*].at)` deriviert, falls wir je eine ähnliche Architektur brauchen — oder bleibt einfach eine normale Column im Server-Modell. (MVP: Column reicht.) ## 5 Kern-Entscheidungen für unser Greenfield 1. **`@cards/domain` als Pure-TS-Workspace-Package** — keine Framework-Bindings 2. **Card-Type discriminated union mit `fields`-Slot + `subIndex`-Granularität** — auch wenn MVP nur basic-Karten hat 3. **`ts-fsrs` v5.3.2 hinter dünnem Adapter** — Reviews plaintext, indiziert auf `due` 4. **Cloze als Token-Parser, wenn implementiert** — Anki-kompatible Syntax 5. **Encryption planen, aber MVP-OFF** — Felder-Allowlist von Tag 1 vorsehen ## Was nicht aus mana-monorepo kommt - Marketplace-Backend (eigenständige Phase 12+) - Local-First-Sync-Stack (mana-sync-Federation oder gar nicht) - Mobile-App (PWA reicht) - Komplexe AI-Workbench-Integration --- **Verbindlich:** Phase 1 (Repo-Skelett) ist abgeschlossen. Phase 0 (dieser Read-Day) ist mit diesem Dokument erledigt. Phase 2 (Auth-Föderation) ist als Nächstes dran.