cards/STATUS.md
Till JS 5859e202c5 feat(cards): deck management UI + production auth portal wiring
Deck schema, API routes, and SvelteKit UI for creating and browsing decks
(DeckStack component, inline creation, floating nav). Production compose
updated with PUBLIC_AUTH_WEB_URL so cards-web redirects to auth.mana.how
for login/register instead of the raw API.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 18:50:27 +02:00

533 lines
26 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Cards — Projekt-Status & Onboarding
**Letztes Update:** 2026-05-11 (Auth-Portal + Email-Verification E2E)
**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).
- **30+ saubere Commits** auf `main`. Type-check 4/4 grün, **129 Tests
grün** (66 Domain + 56 API + 7 Web), lokaler E2E-Smoke (Postgres →
API → MinIO → Frontend → Cloze + Image-Occlusion + Anki-Import mit
Media → /stats → /me/export → /cards/hashes) durch.
- **🚀 LIVE seit 2026-05-08** auf `https://cardecky.mana.how` +
`https://cardecky-api.mana.how` (Mac Mini, Cloudflare-Tunnel
`1435166a-…`). Container `cards-{postgres,minio,api,web}`.
Forgejo-Remote `git.mana.how/till/cards`. Public-E2E bestätigt:
Deck + Card via API anlegbar, Manifest exposed,
`cards.*`/`cards-api.*` redirecten via nginx-301 zu cardecky.*.
- **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-protocol` ersetzt, Spec-Drift
`mana/url``mana/link` gefixt. 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.json` implementiert. 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:
1. **Strategie B (Greenfield).** Kein Code aus mana-monorepo.
2. **Server-authoritative MVP.** Keine Dexie, keine eigene Sync-Engine.
Local-First später via mana-sync-Federation, nicht durch eigenen
Stack.
3. **Eigene Postgres-DB `cards`** mit Schema-Isolation `pgSchema('cards')`.
4. **Föderations-Endpoints als Pflicht** — alle aus `app-manifest.json`
implementiert (siehe Phase 5 unten).
5. **Encryption initial AUS.** Nachrüstbar via mana-auth-MK.
6. **MVP-Card-Types `basic` + `basic-reverse` + `cloze` +
`image-occlusion`** (Cloze Phase 8, Image-Occlusion Phase 9l).
Schema vorbereitet auf type-in, audio, multiple-choice.
Vollständiger Plan: [`mana/docs/playbooks/CARDS_GREENFIELD.md`](../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) | ✅ live 2026-05-08 | App in mana-auth registriert, JWT-Verify additiv mit Dev-Stub-Fallback, E2E gegen `tills95@gmail.com` verifiziert |
| 2b | Auth-Portal (`mana-auth-web` :3002, auth.mana.how) | ✅ 2026-05-11 | SvelteKit-Auth-Portal auf :3002 gebaut. Login/Register/ForgotPassword/Reset/VerifyEmail/TwoFactor. Cards-App redirect zu auth.mana.how statt eigenem Login-Form. Email-Verification E2E verifiziert (mana-notify → mailpit → token → callback → JWT). |
| 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 | 🟡 plumbing | Tier-Awareness im JWT-Claim, requireTier-Helper, credits-client. Nicht produktiv aktiv (Cards-MVP ist tier-frei) |
| 7 | AI/MCP-Integration | ✅ live 2026-05-08 | mana-share + mana-mcp deployed (`share.mana.how`, `mcp.mana.how`), Cards-Manifest registriert, `cards.create` + `cards.search` bei mana-mcp upserted (`tools_upserted=2`) |
| 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, Media, Image-Occlusion) | 🟡 weit | Card-Edit + Cloze-Editor + Inbox-Banner + Account/DSGVO + Statistik + Pre-Flight-Swap + i18n DE/EN + A11y-Pass + Cloze-Hint-Anzeige + Anki-Re-Import-Dedupe + MinIO-Media-Upload + Image-Occlusion durch (9a9l). Verbleibend: type-in, audio, multiple-choice (Schema vorbereitet) |
| 10 | Production-Deploy (Mac Mini, Cloudflare-Tunnel) | ✅ live 2026-05-08 | cardecky.mana.how + cardecky-api.mana.how, alte cards.* via nginx-301-Redirect |
| 11 | Decommission Cards-Modul aus mana-monorepo | ✅ 2026-05-08 | apps/cards, services/cards-server, packages/cards-core, mana-app cards-Modul + cross-refs entfernt (4 Commits, type-check 0 errors) |
| 12 | Marketplace-Restore (R0R6) | 🟢 R0R5 + Polish + 3 Cardecky-Decks live | Plan: [`docs/playbooks/MARKETPLACE_RESTORE.md`](docs/playbooks/MARKETPLACE_RESTORE.md). Stack komplett: R0R5 + G1-G4. Cardecky-Skill (`~/.claude/skills/cards-deck/SKILL.md`) auf Marketplace-Target + Bulk-Mode. **3 Cardecky-Decks live** (Audit-Trail unter `docs/marketplace/seed/`): `/d/geografie-welt-top30` (30, CC0), `/d/english-a2-grundwortschatz` (500, CC-BY-4.0), `/d/periodensystem-elemente` (118, CC0). 648 Karten gesamt, 1296 FSRS-Reviews, alle <1s atomic publish. CONTENT_PLAN §8 Phase-1-Seed-Liste 3/20 done. Verbleibend: 17 weitere Tier-A-Decks, R6 UI-E2E, Anki-ImportMarketplace-Hook. |
| 12-UX | Marketplace-UX-Polish (Subscribe=Fork, Deck-Settings) | 2026-05-11 | Subscribe forkt automatisch (`forkDeckForUser` extrahiert, von POST /subscribe gerufen). Kein separater Fork-Button mehr. Abonnierte Decks auf der Homepage navigieren zu `/study/{id}` wenn ein Fork existiert. `/decks/[id]/edit` zu vollständiger Settings-Page ausgebaut: Allgemein (Name/Desc/Farbe/Kategorie/Sichtbarkeit), Marketplace (Link + Update-Pull), Danger-Zone (Duplizieren + Löschen). Neue Backend-Endpoints: `GET /decks/:id/marketplace-source`, `POST /decks/:id/duplicate`. |
Legende: erledigt + verifiziert · 🚧 blockiert · noch nicht begonnen
---
## Was läuft
### Lokal voll einsatzbereit
**One-Shot (empfohlen):**
```bash
cd /Users/till/Documents/Code/cards
NPM_AUTH_TOKEN=<verdaccio-token> pnpm install # einmalig / nach pull
pnpm dev:full # cards-docker + mana-docker + DB-Push (cards & auth) + dev (cards, mana-auth, mana-auth-web)
```
Für Email-Verification zusätzlich mailpit + mana-notify starten:
```bash
# mailpit (SMTP-Catcher, Web :8025)
docker run -d --name mailpit -p 1025:1025 -p 8025:8025 axllent/mailpit
# mana-notify (Notification-Service, :3066)
cd /Users/till/Documents/Code/mana/services/mana-notify
PORT=3066 DATABASE_URL="postgresql://mana:devpassword@localhost:5432/mana_notify" \
SERVICE_KEY="dev-service-key-for-bot-sso-2024" MANA_AUTH_URL="http://localhost:3001" \
SMTP_HOST="localhost" SMTP_PORT="1025" SMTP_FROM="Mana <noreply@mana.how>" \
SMTP_INSECURE_TLS="true" go run ./cmd/server &
```
Dann: `open http://localhost:8025` Verification-Mails landen hier.
Oder von überall via zsh-Alias: `cards-dev` (definiert in `~/.zshrc`,
zeigt auf `pnpm dev:full` im cards-Repo).
`dev:full` greift bewusst in `../mana/` (Plattform-Repo): startet
`mana-postgres` und `mana-auth` (:3001) parallel zu cards-api/-web.
Login lokal komplett testbar Dev-User `tills95@gmail.com` /
`Aa-123456789` ist in der lokalen `mana_auth`-DB angelegt
(`access_tier=founder`, `email_verified=true`). Cross-Repo-Coupling
ist Trade-off für E2E-Bequemlichkeit; CLAUDE.md erwähnt das nicht
als Architektur-Invariante, sondern Pragma.
**Manuelle Sequenz** (für Debug oder wenn nur Teile gebraucht werden):
```bash
cd /Users/till/Documents/Code/cards
# 1. Dependencies (idempotent)
NPM_AUTH_TOKEN=<verdaccio-token> pnpm install
# 2. Postgres + MinIO-Container (Postgres :5435, MinIO :9100/:9101 —
# kollidiert nicht mit Plattform-:5432/:9000/:9001)
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`](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`](CLAUDE.md)
### Wichtige Dateien
| Pfad | Zweck |
|---|---|
| [`STATUS.md`](STATUS.md) | dieses Dokument Single Source of Truth für Status |
| [`CLAUDE.md`](CLAUDE.md) | Konventionen + Architektur-Invarianten + Stack-Decisions |
| [`README.md`](README.md) | Kurz-Anleitung, ein paar Befehle |
| [`docs/SMOKE_TEST.md`](docs/SMOKE_TEST.md) | Reproduzierbarer E2E-Lauf (curl-Sequenz) |
| [`docs/LESSONS_FROM_MANA_MONOREPO.md`](docs/LESSONS_FROM_MANA_MONOREPO.md) | 15 Architektur-Lessons aus dem Read-Day, 5 Kern-Entscheidungen |
| [`app-manifest.json`](app-manifest.json) | Source of Truth für Föderations-Vertrag (v1.0.0, beta-tier) |
| [`packages/cards-domain/src/`](packages/cards-domain/src/) | zod-Schemas SSOT + FSRS-Adapter + Protocol-Mirror |
| [`apps/api/src/`](apps/api/src/) | Hono-Routen, Drizzle-Schemas, Share-Handlers |
| [`apps/web/src/`](apps/web/src/) | SvelteKit-Routes, $lib/api, $lib/auth-Stub |
| [`infrastructure/docker-compose.yml`](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
```
(aktuell) Auth-Portal: mana-auth-web :3002, cards redirect → auth.mana.how, email verification E2E
Marketplace-UX-Polish: Subscribe=Fork+Track, Deck-Settings-Page
39b1791 Phase 9l: Image-Occlusion als 4. MVP-CardType
c9eb0a6 Phase 9k: Media-Upload via MinIO-Container
e7ae93d docs: STATUS.md auf Phase-9-Welle-2-Stand
593d447 Phase 9j: Anki-Re-Import-Dedupe via content_hash
4b451f1 Phase 9i: Cloze-Hint-Anzeige
fd86d96 Phase 9h: A11y-Pass
c25c1d0 Phase 9g: i18n DE/EN über alle Routes
a640594 docs: STATUS.md auf Phase-9-Polish-Stand
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-hono` `authMiddleware()` mit JWKS-Cache
- Web: `@mana/shared-auth`-Login-Flow gegen `auth.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 FilenameZIP-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`.
### 11. MinIO-Media-Storage (Sprint 9k)
`cards-minio`-Container im `infrastructure/docker-compose.yml` auf
9100/9101 (Plattform-MinIO ist 9000/9001 wir bleiben isoliert).
`apps/api/src/services/storage.ts` ist ein dünner Wrapper um den
`minio`-Client; `ensureBucket()` ist idempotent. ObjectKey-Format
`<userId>/<ulid>.<ext>` ermöglicht Bucket-Prefix-Sweep beim
DSGVO-Delete (kein S3-Cascade).
Konfiguration via `CARDS_S3_*`-env-Vars; Default lokaler Container.
Phase-10-Prod kann gegen denselben Container auf dem Mac Mini laufen
(eigener Bucket) oder gegen Plattform-MinIO. Keine Code-Änderung,
nur env.
### 12. Image-Occlusion (Sprint 9l)
Field-Schema: `image_ref` zeigt auf eine `media_files.id` (Sprint 9k
Storage), `mask_regions` ist ein JSON-Array mit Schema:
`{ id, x, y, w, h, label? }`, alle Coords 0..1 relativ.
`subIndexCount('image-occlusion')` wirft analog zu cloze; Card-POST
nutzt `maskRegionCount(mask_regions)`.
Editor (`ImageOcclusionEditor.svelte`): SVG-Overlay über `<img>`,
Drag-to-create für Masken, Mindestgröße 2% damit Klicks gefiltert
werden. Touch-tauglich via Pointer-Events. Mask-Liste mit Inline-
Label-Edit + Delete.
Study-View (`ImageOcclusionView.svelte`): aktive Maske ist im Prompt
opake schwarz, im Reveal transparent grün mit Label-Text-Overlay
(SVG-Text mit stroke-Outline gegen Bild-Hintergründe). Andere Masken
bleiben dezent gelb-durchsichtig als Lern-Hinweis.
### 10. content_hash auf cards (Sprint 9j)
Jede neue Karte bekommt einen SHA-256-`content_hash` über
`{type, sorted-fields}` geschrieben automatisch im Card-POST,
genutzt vom Anki-Re-Import-Dedupe (`/api/v1/cards/hashes` lädt
nur die Hash-Liste, der Importer dedupliziert clientseitig).
`cardContentHash()` in `@cards/domain` ist deterministisch und
field-order-invariant. Cluster-Markup, Whitespace und Hint-
Annotationen zählen mit bewusst, weil zwei Cards mit dem Text
`Paris ist Hauptstadt` und `{{c1::Paris}} ist Hauptstadt` sind
inhaltlich verschieden.
**Backfill-Lücke:** Pre-Sprint-9j-Karten haben `content_hash = NULL`.
Der `/hashes`-Endpoint filtert sie weg, also können sie irrtümlich
beim Re-Import erneut entstehen. Backfill-Skript ist Phase-10-Polish.
### 11. 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
1. **Lies dieses Dokument zu Ende** (5 Min).
2. **Lies `CLAUDE.md`** (Konventionen + Stack-Decisions, 3 Min).
3. **Lies `mana/docs/playbooks/CARDS_GREENFIELD.md`** (Master-Plan, 10 Min).
4. **Optional:** `docs/LESSONS_FROM_MANA_MONOREPO.md` für Domain-Verständnis.
5. **Verifiziere lokal:** `docs/SMOKE_TEST.md` durchspielen wenn
alle 7 Schritte grün, ist die Umgebung in Ordnung.
6. **Memory-Check (KI):** falls du auto-memory hast, prüfe
`memory/project_phasenstand.md` für eventuell neueren Stand.
---
## Was als Nächstes ansteht (Vorschläge)
In Reihenfolge meiner Empfehlung:
1. **Pre-Flight Restklemmen abräumen** DNS, GitHub-Repo
`mana-ev/cards`, mana-auth-App-Reg (Service-Key + Public-Key),
mana-share-Manifest-Reg. Hängt an User-Aktion + laufender
Plattform.
2. **Phase 6 (Subscriptions)** braucht laufende mana-credits +
Phase 2 Auth-Föderation.
3. **Phase 7 (AI/MCP)** braucht laufende mana-mcp.
4. **Phase 10 (Production-Deploy)** Mac Mini + Cloudflare-Tunnel
(cardecky.mana.how + cards-api.mana.how) + MinIO-Bucket
(eigener Container oder Plattform), nach allen Pre-Flight-Items.
5. **content_hash-Backfill** Pre-Phase-9j-Karten haben null
content_hash; ein einmaliges Skript würde sie nachziehen, sodass
Re-Import-Dedupe lückenlos funktioniert. Phase 10-Polish, sobald
Live-User da sind.
6. **Weitere CardTypes** type-in, audio, multiple-choice sind
im Future-Schema vorbereitet, kommen wenn der User-Wunsch klar
ist.
7. **Image-Occlusion-Polish** Resize/Move existierender Masken
(aktuell: nur Draw + Delete), Mask-Reorder, Mehrere Masken in
einem Cluster (mehrere als ein Review).
Was nicht autonom geht: Phase 2, 6, 7, 10 alle hängen an Pre-Flight
oder Plattform-Diensten. Phase 9 ist sehr breit ausgebaut.
---
**Wenn du dieses Dokument liest und etwas hier nicht stimmt, ist das
Dokument schuld, nicht der Code. Update es.**