cards/STATUS.md
Till JS 9da10b3252 Phase 8d: STATUS.md auf Phase-8-Stand aktualisiert
TL;DR-Summary auf 11 Commits / 92 Tests / Cloze + Anki-Import
verschoben. Phasen-Tabelle: 8  mit 92 Tests grün. Architektur-
Subtilitäten ergänzt (#6 Cloze-N-Reviews, #7 Strategie-B-Ausnahme
für Anki-Parser, #8 Media-Drop, #9 sql-wasm.wasm-Pflege; alte #6
Decommission rutscht auf #10). MVP-Card-Type-Set in Punkt 6 der
festgenagelten Architektur-Entscheidungen aktualisiert. Subtilität
#2 (SubIndex-Granularität) verweist jetzt auf #6 für die text-
abhängige Cloze-Variante.

"Was als Nächstes ansteht"-Empfehlung neu sortiert: Card-Edit-Page
+ Pre-Flight-Abräumen (Verdaccio-Token liegt vor) statt Anki-Import
auf Platz 1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 17:46:33 +02:00

386 lines
17 KiB
Markdown
Raw 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-08 (Phase 8)
**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).
- **11 saubere Commits** auf `main`. Type-check 4/4 grün, **92 Tests
grün** (41 Domain + 46 API + 5 Web), lokaler E2E-Smoke (Postgres →
API → Frontend → Föderations-Endpunkte → Cloze-Card mit 2-Cluster-
Sub-Index) durch.
- **Phasen 0, 1, 3, 4, 5, 8 sind durch** und verifiziert. Phase 2
(Auth-Föderation) ist auf user-side Pre-Flight blockiert.
- 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).
```
┌─────────────────────────────────┐
│ 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`** (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`](../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. Media + Editor-UI bewusst out-of-scope |
| 9 | Polish (DSGVO-UI, Settings, Account, Statistik, i18n, A11y) | ⏸ offen | breite Polish-Phase |
| 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
```bash
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`](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
```
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. Lokales Protocol-Mirror
`packages/cards-domain/src/protocol/` enthält 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 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
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. **Card-Edit-Page + Inbox-Banner im Frontend** — 12 Tage Polish.
Schließt die augenfälligste UI-Lücke (Karten kann man anlegen aber
nicht editieren). Cloze-Editor wäre sinnvoll mitzuziehen.
2. **Pre-Flight aktiv abräumen** — Mana-Plattform-Stack + mana-auth
live, dann Cards als App registrieren (Phase 2). NPM_AUTH_TOKEN
für Verdaccio liegt laut Memory bereits vor — der Protocol-Mirror-
Swap auf `@mana/shared-share-protocol` ist eine 1-2h-Arbeit.
3. **Phase 6 (Subscriptions)** — braucht laufende mana-credits.
4. **Phase 9 (Polish)** — Settings, Account, DSGVO-UI, Statistik,
i18n, Hint-Anzeige bei Cloze, Media-Upload für Anki-Import
(additiv zu Phase 8c).
Was nicht autonom geht: Phase 2 (Auth-Föderation), Phase 7 (mana-mcp-
Live), Phase 10 (Mac-Mini-Deploy) — alle hängen an Pre-Flight-Items.
---
**Wenn du dieses Dokument liest und etwas hier nicht stimmt, ist das
Dokument schuld, nicht der Code. Update es.**