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

17 KiB
Raw Blame History

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 (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

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

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.