Commit graph

5 commits

Author SHA1 Message Date
Till JS
0791436107 feat(cards): typing Card-Type mit Fuzzy-Match
- typing.ts: checkTypingAnswer (exact / close / wrong) + Levenshtein-
  Impl; close = Distanz ≤ max(1, floor(len * 0.2)); Alias-Support via
  komma-separiertes aliases-Feld
- CardTypeSchema: 'typing' ergänzt; validateFieldsForType: front+answer required
- subIndexCount: 'typing' → 1
- TypingView.svelte: Input-Feld + Submit + Result-Badge + Antwort-Markdown +
  kontext-spezifische Grade-Buttons (correct: Weiter; close: Nochmal/War richtig;
  wrong: volle 4 Buttons); svelte:window für Keyboard
- Study-Page: TypingView eingebunden, Action-Bar bei typing ausgeblendet,
  onKey delegiert zu TypingView

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 15:23:58 +02:00
Till JS
170a2825a4 feat(cards): audio-front Card-Type
- CardTypeSchema: 'audio-front' als vollwertiger Type (fields: audio_ref + back + front?)
- subIndexCount: 'audio-front' → 1
- AudioFrontView.svelte: custom Play/Pause-Button, audio via /api/v1/media/:id,
  optionaler Hint-Text; Antwort-Markdown läuft über bestehenden answerHtml-Pfad
- Study-Page: isAudioFront + audioFrontData derived, AudioFrontView eingebunden

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 15:18:41 +02:00
Till JS
39b1791fb9 Phase 9l: Image-Occlusion als 4. MVP-CardType
Domain: CardTypeSchema öffnet 'image-occlusion'. Neues Modul
@cards/domain/src/image-occlusion.ts mit MaskRegionSchema (zod-strict,
0..1-relative Coords + optionalem Label, max 100 Regionen),
parseMaskRegions (parse + sort-by-id), maskRegionCount, maskForSubIndex.

Field-Schema (cards.fields):
  image_ref:    string  — media_files.id (Phase 9k Storage)
  mask_regions: string  — JSON-Array<MaskRegion>
  note?:        string  — optionale Bildunterschrift

subIndexCount('image-occlusion') wirft analog zu cloze, weil die
Anzahl text-abhängig ist. Card-POST-Handler ruft maskRegionCount
und lehnt 422 ab, wenn das Mask-Array leer / kaputt ist (vor dem
Deck-Lookup).

UI-Komponenten:
  - ImageOcclusionEditor: File-Picker → uploadMedia (Phase 9k),
    SVG-Overlay über das Bild, Drag-to-create-Rectangle (mind. 2%
    Größe, sonst Klick-Filter), Mask-Liste mit Label-Input und
    Delete-Button. Pointer-Events für Touch-Mobile-Support.
  - ImageOcclusionView (Study-Render): Bild + SVG-Overlay; aktive
    Mask ist im Prompt opake schwarz, im Reveal transparent grün
    mit Label-Text; andere Masken bleiben dezent gelb-durchsichtig
    als Lern-Hinweis.

/cards/new + /cards/[id]/edit: Type-Picker um Image-Occlusion
erweitert, Branch-Logik schaltet auf den Editor um. canSave-
Validierung: imageRef gesetzt + mind. 1 Mask. /study/[deckId]
nutzt ImageOcclusionView statt Markdown-Render. /decks/[id]-Liste
zeigt "🖼 image-occlusion · <ref-prefix>" statt "(leer) → (leer)".

i18n DE/EN: type_image_occlusion, toast_image_occlusion,
image_occlusion-Namespace (image_label, draw_hint,
label_placeholder, delete_mask, no_image_selected, etc.).

Tests: 11 neue Domain-Tests für MaskRegion-Schema/Parse/Mapping
(66 Domain ges.), 3 neue API-Tests für 422-Branches und
Validation-vor-Deck-Lookup-Pfad (56 API ges.). 129 Tests grün
ges. (66 + 56 + 7), type-check 384 files 0 errors, prod-Build
sauber.

E2E-Smoke: Image-Occlusion-Card mit 2 Masken (image_ref auf das
Sprint-9k-Test-PNG) → API legt content_hash + 2 Reviews mit
sub_index 0+1 an, reviews/due returnt sie korrekt typisiert.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 18:50:45 +02:00
Till JS
553a78d73b Phase 8a: Cloze als MVP-Card-Type, Cluster-Counter
CardTypeSchema öffnet 'cloze' als drittes MVP-Set-Mitglied. Domain-Modul
@cards/domain/src/cloze.ts kapselt die Cluster-Logik (extractClusterIds,
subIndexCountForCloze, clusterIdForSubIndex, renderClozePrompt/Answer)
— Hint-Markup wird MVP-stumm gedroppt.

subIndexCount('cloze') wirft jetzt explizit, statt still auf 1 zu fallen,
weil die Cluster-Anzahl text-abhängig ist und ein silent-default falsch
dimensionierte Review-Tabellen produzieren würde. Card-POST-Handler holt
für Cloze die Anzahl aus subIndexCountForCloze und lehnt 422 ab, wenn
kein {{cN::…}}-Markup vorhanden ist.

12 neue Cloze-Tests, alle Domain- und API-Tests grün (41 + 46).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 17:35:39 +02:00
Till
45a47e0ffd Phase 3: Domain-Modell + Decks/Cards/Reviews-CRUD
Domain (@cards/domain):
- zod-Schemas SSOT für Deck, Card, Review, StudySession, FsrsSettings,
  Tools (cards.create + cards.search Input/Output)
- CardType-Discriminated-Union: MVP basic+basic-reverse, Future-Set
  (cloze, type-in, image-occlusion, audio, multiple-choice) für
  Schema-stable-Migration vorbereitet
- validateFieldsForType() Pure-Function pro CardType
- FSRS-Adapter über ts-fsrs v5.3.2: newReview, gradeReview,
  subIndexCount, toFsrsCard/fromFsrsCard ISO↔Date-Roundtrip
- Encryption-Hinweis: reviews bleiben PLAINTEXT (Scheduler quert
  täglich `due <= now`, siehe Lessons §3)

Drizzle-Schemas (apps/api/src/db/schema, alles in pgSchema('cards')):
- decks, cards, card_tags, reviews (PK card_id+sub_index), study_sessions,
  tags (deck-skopiert), media_refs (verweist auf mana-media), import_jobs
- _schema.ts-Pattern um Zirkular-Imports zu vermeiden (Lesson aus
  mana-share/-events während F-0)
- Hot-Path-Index reviews_user_due_idx für Scheduler-Queries

Routes (apps/api/src/routes):
- POST/GET/PATCH/DELETE /api/v1/decks (Deck-CRUD)
- POST/GET/PATCH/DELETE /api/v1/cards (Card-CRUD mit Auto-Reviews-Init:
  beim Card-Insert werden N Reviews via subIndexCount(type) angelegt,
  in einer Transaktion)
- GET /api/v1/reviews/due (Hot-Path, optional deck_id-Filter, Limit 500)
- POST /api/v1/reviews/:cardId/:subIndex/grade (FSRS-State-Transition,
  per-Deck FSRS-Settings)

Auth: Stub-Middleware liest X-User-Id-Header (Phase 2 ersetzt durch
@mana/shared-hono authMiddleware mit JWKS-Cache).

Tests (vitest, Hono app.request()):
- @cards/domain: fsrs.test.ts (newReview, gradeReview Roundtrip,
  Rating-Mapping), schemas.test.ts (zod-strict-Variants, Field-Type-
  Validation, hex-Color)
- apps/api: decks.test.ts + cards.test.ts + reviews.test.ts —
  Auth-Gate + Input-Validation. Volle DB-Integrationstests folgen mit
  pg-mem oder testcontainers in späterer Phase.

Cleanup: types.ts entfernt, zod-Schemas sind SSOT (z.infer für Types).

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