Strategie B (beschlossen 2026-05-08): Cards wird als eigenständige
föderierte App neu gebaut, ohne Code-Übernahme aus mana-monorepo.
Skelett enthält:
- apps/api: Hono+Bun mit /healthz, /version, Manifest-Endpoint, leere
pgSchema('cards'), Drizzle-Config, erstem Vitest
- apps/web: SvelteKit 2 + Svelte 5 (runes), Vite auf 3082
- packages/cards-domain: Pure-TS, CardType-Discriminated-Union,
SubIndex-Granularität für Reviews, Future-CardType-Set vorbereitet
- infrastructure/docker-compose.yml: Postgres 16 auf 5435
- app-manifest.json: v1.0.0, Verein-owned, beta-tier
- .github/workflows/ci.yml
- docs/LESSONS_FROM_MANA_MONOREPO.md (Read-Day-Output, 15 Lehren)
Pre-Flight für Phase 2 (Auth-Föderation): DNS cardecky.mana.how,
GitHub-Repo mana-ev/cards, Cards-App-Registrierung in mana-auth,
NPM_AUTH_TOKEN für Verdaccio.
Plan: mana/docs/playbooks/CARDS_GREENFIELD.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
11 KiB
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<string,string>+subIndex-Granularität - FSRS via
ts-fsrsv5.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<string, string>als generischer Slot (stattfront/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-fsrsv5.3.2 — dependency-free, Date-basierte API- mana-monorepo wraps in
LocalCardReview↔ts-fsrs.CardAdapter (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-IndexrenderCloze(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
__fieldMetapro Record für Konflikt-Detection_updatedAtIndexShadow-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
- Basic:
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
INPUTfocused, ignoriere Hotkeys
→ Bei uns: Phase 4. Dasselbe Pattern. Hotkey-Handler-Helper <KeyboardListener>-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 inAI_TOOL_CATALOGregistriert - 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
updatedAtals synced data field: mana-monorepo musste das nachträglich auf__fieldMeta-deriviert umbauen (siehemana-monorepo/CLAUDE.md§"Conflict-Detection 2026-04-26"). Wir machen es ab Tag 1 richtig:updatedAtwird beim Read ausmax(__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
@cards/domainals Pure-TS-Workspace-Package — keine Framework-Bindings- Card-Type discriminated union mit
fields-Slot +subIndex-Granularität — auch wenn MVP nur basic-Karten hat ts-fsrsv5.3.2 hinter dünnem Adapter — Reviews plaintext, indiziert aufdue- Cloze als Token-Parser, wenn implementiert — Anki-kompatible Syntax
- 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.