TL;DR aktualisiert: 17 Commits, 94 Tests, Phase 9 läuft mit 6 Sprints durch (9a Card-Edit, 9b Cloze-Editor, 9c Inbox-Banner, 9d Protocol- Swap+npm.mana.how, 9e Account/DSGVO-Self-Service, 9f Statistik). Architektur-Subtilität #3 von "lokaler Mirror" auf "Re-Export + historischer Kontext" geschärft. Git-Historie + "Was als Nächstes" auf den neuen Stand gezogen — Schwerpunkt jetzt i18n/A11y, Hint- Anzeige, Media-Upload für Anki-Import. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
18 KiB
Cards — Projekt-Status & Onboarding
Letztes Update: 2026-05-08 (Phase 8 + Phase 9 Polish-Welle) Wenn du gerade neu bist (Mensch oder KI): dieses Dokument soll dir in 5 Minuten den vollen Kontext geben. Lies es vor allem anderen.
TL;DR
- Cards ist die föderierte Spaced-Repetition-App des Vereins
mana e.V. Strategie-B-Greenfield (beschlossen 2026-05-08): kein
Code-Übernahme aus dem alten
mana-monorepo, sauber neu gebaut — mit einer dokumentierten Ausnahme für den Anki-Format-Parser (Phase 8c, standalone Parser-Logik). - 17 saubere Commits auf
main. Type-check 4/4 grün, 94 Tests grün (41 Domain + 48 API + 5 Web), lokaler E2E-Smoke (Postgres → API → Frontend → Föderations-Endpunkte → Cloze-Card → /stats → /me/export) durch. - Phasen 0, 1, 3, 4, 5, 8 vollständig durch. Phase 9 Polish-
Welle teilweise (Card-Edit, Cloze-Editor, Inbox-Banner, Account-
/DSGVO-Self-Service, Statistik-Dashboard) — i18n + Image-Occlusion
- Hint-Anzeige bei Cloze stehen noch offen. Phase 2 (Auth- Föderation) ist auf user-side Pre-Flight blockiert.
- Pre-Flight teilweise abgeräumt (Sprint 8d): Verdaccio-Token
produktiv via npm.mana.how, lokaler Protocol-Mirror durch
Re-Exports aus
@mana/shared-share-protocolersetzt, Spec-Driftmana/url→mana/linkgefixt. Verbleibend Pre-Flight: DNS, GitHub-Repo, mana-auth-App-Reg, mana-share-Manifest-Reg. - Cards läuft lokal, ist im Browser benutzbar, hat alle Föderations-
Endpoints aus dem
app-manifest.jsonimplementiert. Anki-Decks können importiert werden (Cloze first-class), Karten manuell editiert, Statistiken angeschaut, Daten via DSGVO-Self-Service exportiert/gelöscht.
┌─────────────────────────────────┐
│ cards/ (this repo) │
│ │
│ apps/web/ SvelteKit + Svelte 5 ← cardecky.mana.how
│ apps/api/ Hono + Bun + Drizzle ← Postgres `cards`
│ packages/cards-domain/ Pure-TS ← FSRS, Schemas, Protocol-Mirror
│ app-manifest.json Föderations-Vertrag
└─────────────────────────────────┘
▲
│ HTTP, JWT, Manifest
▼
┌─────────────────────────────────┐
│ mana/ Plattform │
│ mana-auth, mana-share, │
│ mana-links, mana-mcp, │
│ mana-search, mana-credits, … │
└─────────────────────────────────┘
Architektur-Entscheidungen (festgenagelt)
Diese stehen — nicht ohne explizite Diskussion antasten:
- Strategie B (Greenfield). Kein Code aus mana-monorepo.
- Server-authoritative MVP. Keine Dexie, keine eigene Sync-Engine. Local-First später via mana-sync-Federation, nicht durch eigenen Stack.
- Eigene Postgres-DB
cardsmit Schema-IsolationpgSchema('cards'). - Föderations-Endpoints als Pflicht — alle aus
app-manifest.jsonimplementiert (siehe Phase 5 unten). - Encryption initial AUS. Nachrüstbar via mana-auth-MK.
- MVP-Card-Types
basic+basic-reverse+cloze(Cloze in Phase 8 ergänzt). Schema vorbereitet auf full-set (type-in, image-occlusion, audio, multiple-choice).
Vollständiger Plan: mana/docs/playbooks/CARDS_GREENFIELD.md
(im Plattform-Repo, weil er Verein-übergreifend gilt).
Phasen-Status
| # | Phase | Status | Verifikation |
|---|---|---|---|
| 0 | Read-Day mana-monorepo-Cards-Code lesen | ✅ | docs/LESSONS_FROM_MANA_MONOREPO.md |
| 1 | Repo-Skelett (Turbo, pnpm, Bun, Docker, CI) | ✅ | pnpm install durch, 136 packages |
| 2 | Auth-Föderation (mana-auth Registrierung, JWT-Verify) | 🚧 blockiert | siehe „Pre-Flight" unten |
| 3 | Domain-Modell + Drizzle + CRUD-API | ✅ | 8 Tabellen, FSRS via ts-fsrs, 46 Tests grün, E2E-Smoke durch |
| 4 | Frontend-Core (SvelteKit, Tailwind 4, Markdown-Editor, Study-View) | ✅ | type-check + build grün, manuell testbar im Browser |
| 5 | Föderations-Endpunkte (share, tools, search, dsgvo) | ✅ | 70 Tests grün, E2E-Smoke (Quote→Inbox→Search→DSGVO-Roundtrip) |
| 6 | Subscriptions/Credits via mana-credits | ⏸ offen | autonom möglich |
| 7 | AI/MCP-Integration | ⏸ offen | braucht laufende mana-mcp |
| 8 | Anki-Import (.apkg-Parser, Cloze-Support) | ✅ | 92 Tests grün, /import-Route benutzbar, Cloze als 3. MVP-Card-Type |
| 9 | Polish (DSGVO-UI, Settings, Account, Statistik, i18n, A11y) | 🟡 läuft | Card-Edit + Cloze-Editor + Inbox-Banner + Account/DSGVO + Statistik durch (9a-9f). i18n, A11y-Pass, Hint-Anzeige bei Cloze, Image-Occlusion und manuelle Cloze-Hint-UI offen |
| 10 | Production-Deploy (Mac Mini, Cloudflare-Tunnel) | ⏸ offen | braucht DNS + Tunnel-Config |
| 11 | Decommission Cards-Modul aus mana-monorepo | ⏸ offen | erst nach Phase 10 |
Legende: ✅ erledigt + verifiziert · 🚧 blockiert · ⏸ noch nicht begonnen
Was läuft
Lokal voll einsatzbereit
cd /Users/till/Documents/Code/cards
# 1. Dependencies (idempotent)
pnpm install
# 2. Postgres-Container (auf :5435, kollidiert nicht mit Mana-Plattform-:5432)
pnpm docker:up
# 3. Drizzle-Schema pushen
cd apps/api
DATABASE_URL='postgresql://cards:cards@localhost:5435/cards' \
pnpm exec drizzle-kit push --force
# 4. API starten (auf :3081)
DATABASE_URL='postgresql://cards:cards@localhost:5435/cards' \
CARDS_API_PORT=3081 \
CARDS_DSGVO_SERVICE_KEY='msk_test_dsgvo_42' \
bun run src/index.ts &
# 5. Web starten (auf :3082)
cd ../web && pnpm dev &
# 6. Browser öffnen
open http://localhost:3082
Login mit beliebigem User-ID-String (Dev-Stub speichert via
sessionStorage). Für Föderations-Endpunkte (share/receive) muss die
User-ID UUID-formatiert sein, z.B. 00000000-0000-0000-0000-00000000aaaa.
Aufräumen: kill %1 %2 && pnpm docker:down (Daten in
infrastructure/.volumes/cards-postgres).
Vollständiger Smoke-Test-Runbook: docs/SMOKE_TEST.md.
Verifizierte Endpoints
GET /healthz → {"status":"ok"}
GET /version → {"app":"cards","version":"…","build":"…"}
GET /.well-known/mana-app.json → Manifest
POST /api/v1/decks User-JWT CRUD
GET /api/v1/decks User-JWT
GET /api/v1/decks/:id User-JWT
PATCH/DELETE /api/v1/decks/:id User-JWT
POST /api/v1/cards User-JWT Create + Auto-Reviews
GET /api/v1/cards?deck_id=… User-JWT
GET/PATCH/DELETE /api/v1/cards/:id User-JWT
GET /api/v1/reviews/due User-JWT Hot-Path
POST /api/v1/reviews/:cardId/:subIndex/grade User-JWT FSRS-Transition
POST /api/v1/share/receive User-JWT Föderations-Inbox
POST /api/v1/tools/:name User-JWT cards.create | cards.search
GET /api/v1/search?q=… User-JWT SearchResultEnvelope
GET /api/v1/dsgvo/export?user_id=… Service-Key
POST /api/v1/dsgvo/delete Service-Key
Pre-Flight für Phase 2 + Live-Föderation
Diese Items sind nicht autonom machbar — du oder ein Mensch musst sie freischalten:
| Item | Wer | Status |
|---|---|---|
DNS für cardecky.mana.how reservieren (Cloudflare) |
Mensch | offen |
GitHub-Repo mana-ev/cards anlegen + Remote pushen |
Mensch | offen |
Cards in mana-auth.apps registrieren (Service-Key + Public-Key) |
Mensch oder KI gegen laufende mana-auth | offen |
NPM_AUTH_TOKEN für Verdaccio in ~/.npmrc setzen |
Mensch | offen |
| Cards-Manifest bei mana-share registrieren | KI gegen laufende mana-share | offen |
Sobald NPM_AUTH_TOKEN da ist, kann der lokale Protocol-Mirror in
packages/cards-domain/src/protocol/ durch Re-Exports aus
@mana/shared-share-protocol ersetzt werden — das ist eine 1-Liner-
Änderung in cards-domain/src/index.ts plus Imports.
Wichtige Pointer
Konventionen + Stack
- pnpm 9.15.x, Node 20+, Bun für apps/api
- Tabs-Indent, single-quotes, 100-col Prettier (
.prettierrc.json) - SvelteKit 2 + Svelte 5 (runes-only — kein legacy
let count = 0) - Hono + Bun + Drizzle für API
- Drizzle 0.38 / drizzle-kit 0.30 / zod 3 (gleicher Stand wie Mana-Plattform)
- Tailwind 4 via
@tailwindcss/vite(oklch-Theme + Dark-Mode-Auto) - Tests: Vitest + Hono
app.request(), später Playwright für e2e
Volle Konventionen: CLAUDE.md
Wichtige Dateien
| Pfad | Zweck |
|---|---|
STATUS.md |
dieses Dokument — Single Source of Truth für Status |
CLAUDE.md |
Konventionen + Architektur-Invarianten + Stack-Decisions |
README.md |
Kurz-Anleitung, ein paar Befehle |
docs/SMOKE_TEST.md |
Reproduzierbarer E2E-Lauf (curl-Sequenz) |
docs/LESSONS_FROM_MANA_MONOREPO.md |
15 Architektur-Lessons aus dem Read-Day, 5 Kern-Entscheidungen |
app-manifest.json |
Source of Truth für Föderations-Vertrag (v1.0.0, beta-tier) |
packages/cards-domain/src/ |
zod-Schemas SSOT + FSRS-Adapter + Protocol-Mirror |
apps/api/src/ |
Hono-Routen, Drizzle-Schemas, Share-Handlers |
apps/web/src/ |
SvelteKit-Routes, $lib/api, $lib/auth-Stub |
infrastructure/docker-compose.yml |
Postgres-Container für lokal-dev |
Cross-Repo-Dokumente (im Plattform-Repo mana/)
| Pfad | Zweck |
|---|---|
mana/docs/playbooks/CARDS_GREENFIELD.md |
Master-Playbook (alle Phasen, Pre-Flight, Decommission) |
mana/docs/FEDERATION.md |
Föderations-Architektur-Grundlagen |
mana/docs/SHARE_PROTOCOL.md |
Manifest- + Envelope-Schema-Spezifikation |
mana/docs/MANA_AUTH_FEDERATION.md |
App-Identitäts-Modell (Service-Keys, JWKS) |
mana/docs/SHARED_PACKAGES.md |
Versions-Disziplin Klasse A/B/C |
mana/docs/PORTS.md |
Port-Allokation (cards-api: 3081, cards-web: 3082) |
mana/docs/PLAN.md |
Übergreifende mana-e.V.-Roadmap inkl. Phase 6 (Cards-Greenfield) |
Git-Historie
6db6dc3 Phase 9f: Statistik-Dashboard
03117d5 Phase 9e: Account-Page mit DSGVO-Self-Service
aff4d95 Phase 9d: Pre-Flight — Protocol-Mirror durch upstream ersetzt
47419b3 Phase 9c: Inbox-Banner auf /decks und /study
35366ed Phase 9b: Cloze-Editor in /cards/new
0a40367 Phase 9a: Card-Edit-Page für alle 3 CardTypes
9da10b3 Phase 8d: STATUS.md auf Phase-8-Stand aktualisiert
2ca09fe Phase 8c: Anki-Import via portiertem Parser
0b609c4 Phase 8b: Cloze-Render im Study-View
553a78d Phase 8a: Cloze als MVP-Card-Type, Cluster-Counter
2bed282 docs: STATUS.md als Single Source of Truth für Cards-Onboarding
0328caa Phase 5: Föderations-Endpunkte — Cards ist föderierter Peer
89a7a92 Phase 4: Frontend-Core MVP — Decks, Cards, Study mit FSRS-Loop
e3b3a2b docs: SMOKE_TEST.md — verifizierter E2E-Lauf gegen lokale Postgres
5f67bd9 Phase 3 follow-up: type-check + tests grün, ts-fsrs v5 API
45a47e0 Phase 3: Domain-Modell + Decks/Cards/Reviews-CRUD
8605b1b Phase 0+1: Repo-Skelett für Cards-Greenfield
git remote -v ist leer — Repo lebt lokal, GitHub-Remote folgt mit
Pre-Flight (mana-ev/cards).
Architektur-Subtilitäten, die nicht offensichtlich sind
1. Reviews bleiben PLAINTEXT
Der FSRS-Scheduler quert täglich due <= now. Wenn die Reviews
verschlüsselt wären, müsste man jeden Tag N Reviews entschlüsseln nur
um zu wissen welche fällig sind. Geht nicht.
→ Wenn Encryption nachgerüstet wird: nur cards.fields (front/back)
und decks.{name,description} werden encrypted, Reviews bleiben
plaintext. Pattern aus mana-monorepo bestätigt (crypto/registry.ts
hat cardReviews plaintext-allowlisted).
2. SubIndex-Granularität pro Card-Type
Eine basic-reverse-Karte hat 2 Reviews (sub_index 0 = front→back,
sub_index 1 = back→front). Cloze hat 1 Review pro Cluster-Index.
Beim Card-Insert werden alle initialen Reviews in einer Transaktion
mit angelegt — siehe apps/api/src/routes/cards.ts POST-Handler.
subIndexCount(type) in @cards/domain ist die SoT für statische
Typen. Für Cloze siehe Subtilität #6 — subIndexCountForCloze(text) ist
die SoT, weil die Anzahl text-abhängig ist.
3. Protocol-Mirror auf upstream umgestellt (Sprint 8d, 2026-05-08)
packages/cards-domain/src/protocol/ war ursprünglich ein lokaler
Mirror, ist seit Sprint 8d ein dünner Re-Export von
@mana/shared-share-protocol@0.1.0 aus Verdaccio (npm.mana.how). Die
gemeinsamen Schemas (Envelope, Search, Quote/Link/Text-Payloads) kommen
direkt aus dem Föderations-Vertrag, nur die Cards-spezifische
Akzeptanz-Map (PAYLOAD_SCHEMAS, validatePayloadForType) bleibt
lokal. Drift-Risiko ist damit beseitigt — pnpm update zieht
automatisch nach.
Repo-.npmrc zeigt auf npm.mana.how (nicht pkg.mana.how wie
zuvor — der Tunnel wurde am 2026-05-07 zurückgerollt). NPM_AUTH_TOKEN
muss als env-var oder im Shell-Profile vor pnpm install gesetzt sein.
3-historisch. Lokales Protocol-Mirror (vor Sprint 8d)
packages/cards-domain/src/protocol/ enthielt eine TEMPORARY-
Kopie der Schemas aus @mana/shared-share-protocol. Solange Verdaccio
nicht offen ist (kein NPM_AUTH_TOKEN), halten wir sie hier lokal.
→ Drift-Risiko: bei jedem Update der mana-Spec MUSS diese Datei nachgezogen werden, bis der Swap erfolgt. Marker-Kommentar oben in jeder Mirror-Datei.
4. Inbox-Deck wird auto-erstellt
Eingehende Shares (über /share/receive) landen alle in einem
auto-erstellten "Inbox"-Deck pro User. ensureInboxDeck(db, userId)
prüft auf Existenz oder legt es neu an. User kann Karten später in
echte Decks umsortieren.
→ Naming-Hinweis: Wenn ein User schon ein Deck namens "Inbox" hat,
greift unser ensureInboxDeck darauf zu. Das ist gewollt (idempotent).
5. Dev-Auth via X-User-Id ist EXPLICIT temporär
apps/api/src/middleware/auth.ts und apps/web/src/lib/auth/dev-stub.svelte.ts
sind beide klar als „Phase 2 ersetzt durch echtes JWT" markiert. Beim
Swap auf mana-auth:
- API:
@mana/shared-honoauthMiddleware()mit JWKS-Cache - Web:
@mana/shared-auth-Login-Flow gegenauth.mana.how - Beide aus Verdaccio (= NPM_AUTH_TOKEN-blockiert)
6. Cloze-Karten haben N Reviews — sub_index pro Cluster
subIndexCount('cloze') wirft bewusst, weil die Anzahl text-abhängig
ist. Caller müssen subIndexCountForCloze(text) aus @cards/domain
nutzen. Cluster werden nach numerischer ID aufsteigend sortiert
({{c1::…}} = sub_index 0, {{c2::…}} = 1, …). Der Card-POST-Handler
lehnt type=cloze ohne mindestens ein Cluster mit 422 ab — eine Cloze
ohne {{cN::…}}-Markup ist sinnlos.
Render-Helpers (renderClozePrompt / renderClozeAnswer) leben in
@cards/domain/src/cloze.ts, sind 12-fach unit-getestet und werden
vom Study-View dünn konsumiert. Hint-Markup ({{c1::answer::hint}})
wird MVP-stumm gedroppt — Hint-Anzeige ist Phase-9-Polish.
7. Anki-Parser ist eine bewusste Strategie-B-Ausnahme
apps/web/src/lib/anki/parse.ts und lib/components/AnkiImport.svelte
sind aus mana-monorepo portiert. Kennzeichnung im Header-Kommentar.
Begründung: Anki-Format-Logik ist standalone Parser-Code (jszip +
sql.js), kein Architektur-Schmuggel — die Kopie spart 2-3 Tage
Re-Implementierung bei null Strategy-Risiko.
import.ts wurde NICHT portiert: das Original schreibt gegen Dexie-
Stores und bricht damit Architektur-Invariante #1 (server-authoritative
MVP). Die neue Version ist von Hand geschrieben und nutzt direkt
$lib/api/{decks,cards} über HTTP.
8. Media-Refs werden beim Anki-Import gedroppt (Phase 8 MVP)
sanitizeAnkiHtml strippt <img> und [sound:…]-Markup ersatzlos.
Späterer Media-Pfad ist additiv — entweder ein lokaler
POST /api/v1/media/upload in cards-api oder gegen Plattform-mana-media
nach Phase 2. Die Filename→ZIP-Map liegt im ParsedAnki.mediaByFilename
weiterhin bereit, sie wird aktuell nur für die Preview-Anzeige
("X Medien werden nicht übernommen") genutzt.
9. sql-wasm.wasm liegt unter apps/web/static/
660kB Build-Asset. Wird vom Browser einmal geladen (initSqlJs cache
in parse.ts). Bei Update von sql.js muss die Datei neu kopiert
werden: cp apps/web/node_modules/sql.js/dist/sql-wasm.wasm apps/web/static/sql-wasm.wasm.
10. Cards-Modul in mana-monorepo wird nach Live-Gang gelöscht
Strategie-B-Konsequenz: nach cardecky.mana.how live + 2 Wochen Test
folgt ein Decommission-PR in mana-monorepo, der apps/mana/.../modules/cards/,
packages/cards-core/, services/cards-server/ (Marketplace-Backend)
und alle DB-Schemas in mana_platform.cards.* entfernt. Keine zwei
Cards-Welten parallel.
→ Diese Entscheidung ist im Greenfield-Playbook festgehalten und im
älteren CARDS_CUTOVER.md (jetzt überholt) als Strategie-A-Variante
diskutiert worden.
Wenn du gerade neu bist — Onboarding-Sequenz
- Lies dieses Dokument zu Ende (5 Min).
- Lies
CLAUDE.md(Konventionen + Stack-Decisions, 3 Min). - Lies
mana/docs/playbooks/CARDS_GREENFIELD.md(Master-Plan, 10 Min). - Optional:
docs/LESSONS_FROM_MANA_MONOREPO.mdfür Domain-Verständnis. - Verifiziere lokal:
docs/SMOKE_TEST.mddurchspielen — wenn alle 7 Schritte grün, ist die Umgebung in Ordnung. - Memory-Check (KI): falls du auto-memory hast, prüfe
memory/project_phasenstand.mdfür eventuell neueren Stand.
Was als Nächstes ansteht (Vorschläge)
In Reihenfolge meiner Empfehlung:
- Phase 9 weiter ausbauen — i18n DE/EN-Pass über alle Routes,
A11y-Pass, Hint-Anzeige bei Cloze (
{{c1::a::hint}}), Media- Upload für Anki-Import (additiv via lokalem cards-api-Endpoint). Re-Import-Dedupe wäre nice. ~3–5 Tage. - Pre-Flight Restklemmen abräumen — DNS, GitHub-Repo
mana-ev/cards, mana-auth-App-Reg (Service-Key + Public-Key), mana-share-Manifest-Reg. Verdaccio + Protocol-Swap durch (Sprint 8d). Hängt an User-Aktion + laufender Plattform. - Phase 6 (Subscriptions) — braucht laufende mana-credits + Phase 2 Auth-Föderation.
- Phase 7 (AI/MCP) — braucht laufende mana-mcp.
- Phase 10 (Production-Deploy) — Mac Mini + Cloudflare-Tunnel, nach allen Pre-Flight-Items.
Was nicht autonom geht: Phase 2, 6, 7, 10 — alle hängen an Pre-Flight oder Plattform-Diensten. Phase 9 weiter ist autonom möglich.
Wenn du dieses Dokument liest und etwas hier nicht stimmt, ist das Dokument schuld, nicht der Code. Update es.