- apps/landing/ entfernt (Cardecky-Marketing-Astro-Site, war nie
deployed — der nginx-Block in landings.conf zeigte auf einen
Pfad ohne Inhalt)
- Stale Doc-Kommentare in api+web: Cardecky → Wordeck wo passend
- fsrs.ts subIndexCount: image-occlusion + audio-front Cases raus
(CardType-Enum hat sie nicht mehr — der throw wäre toter Code)
- fork.ts: image-occlusion-Sonderfall in subIndexCountFor weg
- cards-domain tests: schemas-Test prüft jetzt Wordeck-Typen + dass
image-occlusion/audio-front ABGELEHNT werden
- cards-domain tests: image-occlusion-throw-Test entfernt
- api tests/cards.test.ts: 3 image-occlusion-Cases zu 1 negative
Tested (422 expected, weil Schema sie verbietet)
51 Tests grün (cards-domain).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Behebt live verifiziertes Auth-Bypass auf cardecky-api.mana.how
(X-User-Id → founder-Tier) und zieht im selben Patch das fehlende
Operations-/Compliance-Fundament nach.
* Auth-Middleware fail-secure: opt-in via CARDS_AUTH_DEV_STUB="true"
(war opt-out, Default true). Compose-Default flipped auf "false",
NODE_ENV="production" für cards-api ergänzt, env-Template
dokumentiert. vitest.config.ts + tests/setup.ts aktivieren den
Stub gezielt für Test-Suiten.
* Security-Headers: Hono secureHeaders() in apps/api,
SvelteKit hooks.server.ts mit X-Frame/X-Content-Type/Referrer/
HSTS in apps/web. CSP bewusst ausgelassen — eigener Sprint.
* CORS-localhost-Whitelist nur außerhalb Prod.
* Rate-Limiting (in-memory sliding window, dependency-frei) auf
share.receive 60/min/IP, media.upload 30/min/user,
decks.generate + decks.from-image 10/min/user, dsgvo.* 10/min/IP.
* Health-Endpoint mit echter DB- und MinIO-Probe; /healthz bleibt
Liveness, /healthz/details ist Readiness mit 503 bei Failure.
* DSGVO-Honesty: storage_ok + storage_error im Response (statt
schluckend console.warn), Account-UI zeigt Fehler-Toast.
* Audit-Log: strukturierte JSON-Zeile (kind: "audit") auf stdout
für /dsgvo/export, /dsgvo/delete, /me/export, /me/delete.
* Bug-Fix: duplizierte case "multiple-choice"-Clause in fsrs.ts.
Verifiziert: apps/api 17 Files / 104 Tests grün, apps/web check
0 errors. Deploy auf Mac Mini steht noch aus.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
- `lib/url-fetch.ts`: fetchUrlContent aus decks-from-image herausgezogen
— gemeinsam genutzte Logik für mana-search + direktes HTTP-Fetch-Fallback
- `decks-generate.ts`: optionales `url`-Feld im Input-Schema;
URL-Inhalt wird an den Prompt angehängt wenn vorhanden
- `decks.ts` (web): `generateDeck()` akzeptiert jetzt `url?: string`
- UI: imageUrl wird für Text-KI + Bild-KI als Kontext genutzt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pull-Requests (Diff-Modell add/modify/remove, GitHub-style 3-way
merge in der DB-Transaktion):
- POST /decks/:slug/pull-requests (auth) — neuer PR mit
diff.{add,modify,remove}; previousContentHash für modify identifiziert
die zu ersetzende Karte by content-hash, type bleibt aus dem alten
Eintrag (modify ist field-only Replace)
- GET /decks/:slug/pull-requests (optional-auth) — Liste mit Status-
Filter (open/merged/closed/rejected)
- GET /pull-requests/:id (optional-auth) — Detail
- POST /pull-requests/:id/close (auth) — Author oder Deck-Owner
- POST /pull-requests/:id/reject (auth) — nur Deck-Owner; getrennt
von close, damit der PR-Author klares Feedback hat
- POST /pull-requests/:id/merge (auth) — nur Deck-Owner; baut neue
card-list aus latest version + diff (removes weglassen, modifies
fields-replace, adds anhängen mit re-counted ord), schreibt
publicDeckVersions + publicDeckCards atomar in einer Drizzle-
Transaction, bumpt latestVersionId und setzt PR auf merged.
Default-Semver-Bump: minor (1.0.0 → 1.1.0). Authorenüberschreibbar
via mergeNote/newSemver-Body-Felder.
Card-Discussions (Threads pro card_content_hash, überleben
Versions-Bumps solange Karten-Inhalt bleibt):
- POST /decks/:slug/cards/:hash/discussions (auth) — neuer Thread
oder Reply (parent_id muss in derselben card_content_hash-Gruppe
leben → 422 sonst)
- GET /cards/:hash/discussions (optional-auth) — Liste sichtbarer
Comments, hidden gefiltert
- GET /decks/:slug/discussions/counts (optional-auth) — Bulk-Count
pro card_content_hash für Deck-Übersicht (kein N+1)
- POST /discussions/:id/hide (auth) — Soft-Hide (Author oder Deck-
Owner); kein Delete, Audit-Trail bleibt
Helpers:
- lib/marketplace/semver.ts — bumpMinor, isSemver, semverGreater
(klein, ohne Range-Logik). Wird von PRs + später vom decks.ts
publish-Flow konsumiert.
Bug-Fix:
- routes/marketplace/fork.ts hatte r.use('*', authMiddleware) am
Anfang. An dem /api/v1/marketplace-Mount-Punkt fängt das Wildcard
alle nachfolgenden Router-Mounts (PRs, Discussions) → anonymer
GET /pull-requests wurde mit 401 abgelehnt. Refactor auf per-
route authMiddleware (Pattern wie in subscriptions.ts und
engagement.ts seit R3). Lessons learned dokumentiert in der
STATUS.md-Zeile.
Verifikation:
- type-check 0 errors
- 11 neue Semver-Tests, 89 gesamt grün
- E2E-Smoke gegen lokale cards-api durch:
· Cardecky publisht v1.0.0 (Apatheia, Eudaimonia, Logos)
· Till's PR: modify Eudaimonia-Back, remove Logos, add Tugendlehre
· Till's Merge-Versuch → 403 (deck_owner_only)
· Cardecky merged → v1.1.0 atomar, card_count=3, ord-Reihenfolge:
[Apatheia, Eudaimonia-mit-neuem-Back, Tugendlehre]
· Re-Merge → 409 (pr_already_merged)
· Till's Discussion-Thread + Cardecky-Reply mit parent_id
· Cross-Card-parent abgelehnt → 422
· Hide → Comment verschwindet aus Liste, total von 2 auf 1
· Bulk-Counts liefert {hash → 2}
· Smart-Merge-Pull v1.0.0→v1.1.0 wertet PR-Merge korrekt aus
(changed=2 via Eudaimonia + Logos↔Tugendlehre ord-Heuristik)
Verbleibend: R5 Frontend-Routes (/explore, /d/[slug], /u/[slug],
/me/{published,subscribed,forks}), R6 voller UI-E2E.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Routes (additiv unter /api/v1/marketplace/*):
- POST/GET /authors/me — eigenes Author-Profil anlegen/updaten/lesen
- GET /authors/:slug — public Profile-Lookup (banned-reason gestrippt)
- POST /decks — Deck-Init (Slug-Validation + Pflicht-Author-Profil +
CHECK auf paid + Pro-License)
- POST /decks/:slug/publish — Versions-Snapshot mit per-Karte
cardContentHash aus @cards/domain, per-Version-Hash, AI-Mod-Stub-Log,
atomarer latest_version_id-Bump in Drizzle-Transaction
- PATCH /decks/:slug — Metadaten-Update (Owner-Only)
- GET /decks/:slug — Public-Detail mit optional-auth-Middleware
Geport aus cards-decommission-base:services/cards-server/, mit
Greenfield-Anpassungen:
- Hashing über @cards/domain.cardContentHash (gemeinsame SoT
zwischen privatem cards.cards und marketplace.deck_cards), per-
Version-Hash als SHA-256 über sortierte Karten-Hashes mit Ord-Prefix
- AI-Moderation als R2-Stub (pass+rationale+model='stub'),
echte mana-llm-Anbindung in späterer Welle
- Auth-Middleware-Shape an Greenfield (userId/tier/authMode in
c.get(...) statt user-Object), optional-auth als Schwester für
anonymen Public-Read
- Hono-typing: outer Marketplace-Decks-Router ist Partial<AuthVars>
weil Public-GET kein JWT braucht; Auth-Subroute ist strict
Lese-Referenz:
- 3331 LOC altes cards-server-Code (routes, services, middleware,
lib) unter docs/marketplace/archive/code/ archiviert. Read-only,
nicht im Build-Path.
Verifikation:
- 16 neue Vitest-Tests (Slug + Version-Hash), 72 gesamt grün
- type-check 0 errors
- E2E-Smoke gegen lokale cards-api: Cardecky-Author + Deck
r2-stoische-ethik mit 3 Karten v1.0.0 (basic + basic + cloze),
per-Karten-Hashes geschrieben, ai_moderation_log-Row da, semver-409
+ paid-422-Errors verifiziert. Smoke-Daten danach aufgeräumt.
Verbleibend für R3+: Discovery (explore + search), Engagement (stars/
subscribe/fork), Smart-Merge mit FSRS-State-Erhalt; danach R4 PRs +
Card-Discussions, R5 Frontend-Routes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>