Some checks are pending
CI / validate (push) Waiting to run
- pnpm dev:full chained: docker:up --wait → db:push → concurrently dev + dev:auth
- vite.config: ssr.noExternal + optimizeDeps.exclude für @mana/* aus Verdaccio
(Raw-.ts-Sourcen brechen Node-22-Type-Stripping in node_modules)
- README + STATUS auf neuen Setup-Umfang aktualisiert (mana-auth läuft mit)
- concurrently als devDep, dev:auth/docker🆙auth/db:push:auth Sub-Scripts
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
512 lines
23 KiB
Markdown
512 lines
23 KiB
Markdown
# Cards — Projekt-Status & Onboarding
|
||
|
||
**Letztes Update:** 2026-05-08 (Phase 8 + Phase 9 erweiterte 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).
|
||
- **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 |
|
||
| 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 (9a–9l). 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) |
|
||
|
||
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)
|
||
```
|
||
|
||
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
|
||
|
||
```
|
||
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 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`.
|
||
|
||
### 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.**
|