Commit graph

11 commits

Author SHA1 Message Date
Till JS
aece169360 chore(lint): SwiftLint-Config + 0-Warnings-Pass + Swift-6-Concurrency-Fixes
Bringt cards-native auf 0 SwiftLint-Violations bei 75 Files. Build-Status
unverändert grün (xcodebuild iOS Debug).

.swiftlint.yml
- identifier_name excludes erweitert um math/index-Konventionen
  (i, j, n, m, x, y, w, h, r, g, b, a, c, d, s, f, p, q, t, l) —
  in algorithmischem Code klarer als verbose
- opening_brace disabled — kollidiert mit SwiftFormats
  wrapMultilineStatementBraces (SwiftFormat ist im Pre-Commit-Hook
  und gewinnt)

Code-Modernisierungen (real, nicht nur Annotations)
- Cloze.swift: regex-Tuple bekommt `swiftlint:disable large_tuple`-
  Region — Regex-Output-Type ist Builder-bedingt nicht reduzierbar
- Media.swift: `data(using: .utf8)` → `Data(s.utf8)` (non-failable),
  `String(data:as:)` → `String(bytes:encoding:)`
- CardsTheme.swift: HSL-Wert-Typ statt anonymes 3-Tupel —
  konkretere Call-Sites, kein `large_tuple`-Warning mehr
- MediaCache.swift: `CacheEntry`-Struct statt 3-Tupel im Prune-Pfad
- GradeQueue / MediaCache / StudySession / MarketplaceStore: OSLog-
  Interpolations auf lokale Variablen ziehen — fixt Swift-6-Strict-
  Concurrency-Fail bei Actor-isolated-Property-Zugriff aus
  @Sendable-Autoclosure
- DeckMutations.swift, MarketplaceModeration.swift: verschachtelte
  VersionInfo-Sub-Types auf Top-Level (`PullUpdateVersion`,
  `OwnedMarketplaceVersion`) — fixt `nesting`-Warning
- Tests/UnitTests/*.swift: alle `""".data(using: .utf8)!` migriert auf
  `Data("""…""".utf8)`; force-cast `as!` in MutationEncodingTests
  durch guard-let + throw ersetzt

Pragmatische Disables (mit Doc-Comment-Begründung)
- DeckEditorView / MarketplacePublishView / DeckDetailView /
  PublicDeckView / DeckListView / CardEditorView / CardsAPI:
  `swiftlint:disable type_body_length` (+ teilweise file_length)
  als Region-Disable mit `enable` nach dem Struct. Begründung im
  Doc-Comment: Multi-State-Maschinen mit shared Toolbar + Sheets;
  Aufspalten würde nur @Binding-Plumbing produzieren

Auto-Format-Aufräumung
- Redundante `Sendable`-Conformance entfernt (Swift 6 leitet das
  bei Wert-Typen mit Sendable-Mitgliedern automatisch ab)
- EnvironmentValues nutzt jetzt @Entry-Macro statt manueller
  EnvironmentKey-Boilerplate
- Brace-Reformatting + Import-Sortierung auf allen 75 Files

Ergebnis: 80 Warnings + 3 Errors → 0 / 0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 02:04:29 +02:00
Till JS
73f9081fa1 feat(decks): γ-1 bis γ-8 — AI/CSV-Import, Card-Edit, Pull-Update, Marketplace-Publish + Moderation + PDF
Vervollständigt die Cardecky-Web-Parität für Deck- und Card-Workflows.

γ-1+γ-2 (AI-Deck-Generierung)
- 4-Modi-Picker im DeckEditorView Create-Sheet: Leer/KI/Bild/CSV
- POST /api/v1/decks/generate für Text-Prompt + 10/min Rate-Limit-UI
- POST /api/v1/decks/from-image mit PhotosPicker + PDF-Importer
  (max 5 Files, 10 MiB/Bild, 30 MiB/PDF), Multipart-Body in
  CardsAPI+Generation
- Loading-Overlay mit Task-Cancellation, Error-Mapping für 429/413/502

γ-3 (Card-Edit)
- CardEditorView mit Mode .create(deckId:) / .edit(card:)
- Image-Occlusion + Audio-Front behalten bestehenden Media-Ref, solange
  User nicht ersetzt — MediaCache lädt Bild nach
- Type-Picker im Edit-Modus aus (Server-immutable)
- CardEditorPayload + CardEditorMediaFields als Sub-Views

γ-4 (Pull-Update + Duplicate + Archive)
- POST /marketplace/private/:id/pull-update mit Smart-Merge-Anzeige
- POST /decks/:id/duplicate
- Archive-Toggle im Edit-Modus, Server filtert Liste serverseitig
- DeckSecondaryActions als eigenes Sub-View

γ-6 (CSV-Import)
- RFC-4180-ish Parser (Quote-Escape, Header-Detect, BOM-strip)
- Preview-Liste + sequentielle Card-Inserts mit Live-Progress
- Image-Occlusion/Audio-Front werden geskipped (UI flaggt)

γ-7 (Marketplace-Publish) + Follow-up (Report + Block + Re-Publish)
- MarketplacePublishView mit lazy Author-Setup + Init + Publish 1.0.0
- Re-Publish-Modus: Picker für eigene Marketplace-Decks +
  Auto-Semver-Bump (Minor +1)
- MarketplaceCardConverter (typing → type-in, audio-front → skipped,
  image-occlusion → skipped — Server hat keinen MP-Media-Re-Upload)
- Toolbar-Menü auf PublicDeckView: „Deck melden …" + Author-Blockieren
  (App-Store-Guideline 5.1.1(v))
- ReportDeckSheet mit Reason-Picker (6 Kategorien) + optional Message
- BlockedAuthorsView in Settings mit Swipe-Entblocken

γ-8 (PDF-Export)
- DeckPrintView mit SFSafariViewController auf
  cardecky.mana.how/decks/:id/print — iOS Share-Sheet → PDF speichern

Side-Fixes (mid-stream)
- StudySessionView: Card-Aspect-Ratio springt nicht mehr beim Flip
  (Bottom-Bar in ZStack fixer Höhe)
- RootView: Glass-Pille für „Neues Deck"-Accessory + .guest- und
  .twoFactorRequired-Cases nachgezogen
- DeckListView: Account-Toolbar-Button entfernt (Account-Tab unten
  ist alleinige Anlaufstelle)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 02:03:59 +02:00
Till JS
505aa9db19 feat(study): Typing-Karten + Levenshtein-Match-Logik
CardRenderer für typing ist nicht mehr Placeholder. Web-Vorbild:
TypingView.svelte + cards-domain/typing.ts.

Typing.swift (Sources/Core/Domain/):
- check(input:answer:aliases:) → TypingMatch (correct/close/wrong)
- Normalisierung: trim + lowercase + NFD-Decomp + Combining-Marks
  strippen (Diakritika: ä → a)
- Aliases-Support (Komma-getrennt aus card.fields["aliases"])
- Levenshtein-Threshold max(1, floor(len * 0.2)) → "close"

TypingCardView (Features/Study/):
- TextField mit Auto-Focus 0.15s nach onAppear, Return = Submit
- Submit-Button mit Return-Symbol + primary background
- Nach Submit: Badge (✓ Richtig / ≈ Fast / ✗ Falsch) + User-
  Eingabe in „…" Quotes + Divider + erwartete Antwort
- Haptic-Feedback: heavy bei correct, light bei close/wrong
- Reset on card.id change

TypingTests: 8 Tests für check() — exact, case+whitespace,
NFD-Umlauts, aliases, Levenshtein-close (Berln → Berlin),
empty-input, sowie Levenshtein-Helper-Sanity.

Build 8 → 9. 43 Tests grün (war 35).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 17:39:39 +02:00
Till JS
8b1dd5158f feat(study): Multiple-Choice-Karten gerendert
CardRenderer für multipleChoice ist nicht mehr Placeholder. Web-
Vorbild: MultipleChoiceView.svelte.

MultipleChoiceCardView (Features/Study/):
- Lädt Distractors vom Server beim card.id-Wechsel
  (CardsAPI.distractors(deckId, cardId, field, count))
- Versucht erst field=answer, fallback field=back (für Decks mit
  basic/basic-reverse-Karten daneben)
- Fallback auf distractor_pool-Feld (newline-separated) wenn
  Deck zu klein
- 4 Optionen shuffled = [answer + 3 Distractors]
- User-Tap markiert Auswahl (kein erneutes Pick möglich)
- Vor Flip: nur Selected-Hint (primary border)
- Nach Flip: richtige = green-check, falsche-gewählte = red-cross,
  unselected richtige bleibt green-highlight
- Fallback "tooFew" (< 1 Distractor): zeigt Antwort nach Flip
  ohne Auswahl

CardsAPI.distractors → DistractorsResponse {distractors: [String]}.

Typing bleibt Placeholder — eigene UI-Pattern (Text-Input + Diff)
brauchen mehr Design-Arbeit, separate Phase.

Build 7 → 8, 35 Tests grün.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 17:34:07 +02:00
Till JS
e8b898a51d feat(decks): Card-Liste im DeckDetailView + listCards-API
Bisher zeigte DeckDetailView nur 4 Action-Buttons (Lernen,
Hinzufügen, Bearbeiten, Löschen) — Karten waren nur via Study-Loop
sichtbar. User-Feedback: "ich sehe keine Karten im Deck".

Geändert:
- CardsAPI.listCards(deckId:) → [Card] (war nur cardCount via /total)
- CardListResponse: nimmt cards-Array zusätzlich zu total
- DeckDetailView: ScrollView statt VStack, neue Sektion "Karten"
  unter den Action-Buttons mit CardPreviewRow pro Karte
- CardPreviewRow: Type-Icon + Front-Preview (basic/cloze/audio/
  image-occlusion adaptiv) + Type-Label
- task(id:) + refreshable triggern loadCards()
- Nach CardEditor-Save reloaded die Liste

Build 4 → 5.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 16:02:59 +02:00
Till JS
f528ea448a fix: PublicDeckOwner.pseudonym ist Bool, nicht String?
Beim Öffnen eines Marketplace-Decks crashed JSON-Decoder mit
typeMismatch (Expected String, found Bool) auf
owner.pseudonym.

Ursache: Server-Schema (cards/apps/api/src/db/schema/marketplace/
authors.ts) hat pseudonym als `boolean NOT NULL DEFAULT false` —
ein Flag, dass der Autor pseudonym auftritt (Anzeigename verbergen).
Native hatte das fälschlich als String? (Anzeige-Pseudonym) interpretiert.

Fix:
- PublicDeckOwner.pseudonym: String? → Bool
- decoder.decodeIfPresent(String.self) → decode(Bool.self) ?? false
- Test-Fixture: "pseudonym": null → "pseudonym": false

Build 3 → 4.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 15:46:20 +02:00
Till JS
07ada72b0f v0.6.0 — Phase β-5 Marketplace
Voller Marketplace-Flow mit TabBar und Universal-Link-Handler.
Drei Live-Decks (Geografie, English A2, Periodensystem) sind
browse-, abonnier- und lernbar.

- PublicDeckEntry/PublicDeck/PublicDeckVersion/PublicDeckOwner/
  PublicDeckDetail Codable mit snake_case
- ExploreResponse, BrowseResponse, SubscribeResponse
- MarketplaceSort-Enum (recent/popular/trending)
- CardsAPI.explore/browseMarketplace/publicDeck/subscribe/unsubscribe
- MarketplaceStore @Observable mit Explore + Browse States
- ExploreView: Featured + Trending Horizontal-Carousels, Browse-Link
- BrowseView: Searchable + Sort-Picker + List
- PublicDeckView: Header/Metadata/Subscribe — Subscribe löst Auto-Fork
  serverseitig aus, Response liefert private_deck_id, NavigationLink
  zum eigenen Deck
- PublicDeckCard + BrowseRow mit forest-Theme
- RootView: TabBar (Decks/Entdecken/Account) statt Single-View
- Universal-Link-Handler: onOpenURL + onContinueUserActivity für
  https://cardecky.mana.how/d/<slug> und cards://d/<slug>
- associated-domains: applinks:cardecky.mana.how im entitlement
- 5 neue Marketplace-Decoding-Tests (35 Total grün)

Universal-Links funktionieren erst nach AASA-Setup auf
cardecky.mana.how/.well-known/apple-app-site-association
(Web-Aufgabe, heute 404).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 00:51:12 +02:00
Till JS
80eb3708b4 v0.5.0 — Phase β-4 Media + Advanced Card-Types
Alle 7 Card-Types werden gerendert und können erstellt werden.
image-occlusion mit Touch-Drag-Mask-Editor (kein PencilKit — Server-
Schema erlaubt nur Rechtecke), audio-front mit AVAudioPlayer und
File-Picker.

- MediaUploadResponse-DTO, MaskRegion-Codable mit 0..1-Coordinates
- MaskRegions.parse/encode (1:1-Port aus cards-domain, Sortierung
  nach ID lexikographisch)
- CardFieldsBuilder.imageOcclusion mit stringified-JSON-mask_regions
  + audioFront
- CardsAPI.uploadMedia (Multipart, 25 MiB) + fetchMedia (streamed)
- MediaCache actor mit LRU 200 MB (contentModificationDate-Eviction)
- mediaCache Environment-Key
- RemoteImage + AudioPlayerButton SwiftUI-Views
- CardRenderer: imageOcclusion (Mask-Overlay über RemoteImage) +
  audioFront (AudioPlayerButton + back-Text auf Flip)
- MaskEditorView: Touch-Drag-Rechteck, Label-Edit, Delete
- CardEditorView erweitert: PhotosPicker für Image, fileImporter
  für Audio, Magic-Byte-MIME-Detection
- 6 neue Tests für MaskRegions (30 Total grün)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 00:35:36 +02:00
Till JS
cf1160b270 v0.4.0 — Phase β-3 Editor
Voller Editor-Flow für Decks und 5 Card-Types (basic, basic-reverse,
cloze, typing, multiple-choice). image-occlusion + audio-front kommen
mit β-4 (Media). Anki-Import bleibt vorerst aus (Web parsed client-
side, gibt keinen Server-Import-Endpoint zu rufen).

- DeckCreateBody/UpdateBody, CardCreateBody/UpdateBody Encodable
  mit snake_case-CodingKeys, nil-Felder werden weggelassen
- CardFieldsBuilder mit Type-spezifischen Pflicht-Feld-Konstruktoren
- CardsAPI: createDeck/updateDeck/deleteDeck +
  createCard/updateCard/deleteCard
- DeckEditorView (Create + Edit in einer View): Color-Picker mit
  8-Preset-Palette, Category-Picker (11 Kats, deutsche Labels),
  Visibility-Segmented-Control
- CardEditorView mit Type-Picker und dynamischen Feldern je Typ.
  Cloze-Sektion zeigt Live-Cluster-Count und Hint-Syntax-Hinweis.
  image-occlusion/audio-front zeigen β-4-Placeholder
- DeckDetailView mit Action-Buttons (Lernen, Karte hinzufügen,
  Bearbeiten, Löschen mit Confirmation)
- DeckListView: "+"-Button im Toolbar (Leading) für Create-Sheet
- 7 neue Encoding-Tests (24 Unit-Tests + 1 UI-Test grün)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 00:24:43 +02:00
Till JS
3b861af3fb v0.3.0 — Phase β-2 Study-Loop
Voller Lern-Flow mit Web-Parität: fällige Karten via /reviews/due
laden, flip + rate (4 Buttons + Haptic), Grades via Offline-Queue
ans Server-FSRS schicken.

- Card/Review/DueReview DTOs mit snake_case + camelCase-deckId-
  Sonderfall im embedded card-Subobjekt
- CardType-Enum (alle 7 Typen), Rating-Enum mit deutschen Labels
- Cloze-Helper 1:1-Port aus cards-domain (extractClusterIds,
  subIndexCount, clusterId, renderPrompt/Answer, hint)
- CardsAPI.dueReviews(deckId:) + gradeReview(cardId,subIndex,rating,reviewedAt)
- PendingGrade SwiftData-Model + GradeQueue (FIFO-Drain, originaler
  Timestamp bleibt, bei Netzfehler in Queue, Retry beim nächsten Drain)
- StudySession @Observable State-Machine
- CardRenderer für basic, basic-reverse, cloze; Placeholder für
  image-occlusion/audio-front/typing/multiple-choice (β-3/β-4)
- RatingBar mit UIImpactFeedbackGenerator (medium/heavy)
- StudySessionView per NavigationLink aus DeckListView
- 9 neue Tests (Cloze: 8, Review-Decoding: 3), insgesamt 17 grün

Server-authoritative FSRS bleibt — kein ts-fsrs-Port.
Endurance-Test auf realem Gerät steht aus (siehe PLAN.md).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 00:16:11 +02:00
Till JS
f664a00b64 v0.2.0 — Phase β-1 Decks lesen
Deck-Liste mit Web-Parität: alle eigenen Decks aus cardecky-api,
Card-/Due-Counts pro Deck (Web-Pattern: separate Calls), Pull-to-
Refresh, Offline-Read via SwiftData, Inbox-Banner für Marketplace-
Forks.

- Deck-Codable-DTO mit snake_case-CodingKeys (DeckCategory,
  DeckVisibility, FsrsSettings)
- ISO8601-Date-Decoder mit Fractional-Seconds-Toleranz
- CardsAPI.listDecks() + cardCount() + dueCount()
- CachedDeck SwiftData-Model mit lastFetchedAt
- DeckListStore (API + Cache, paralleles Counts-Fetching via TaskGroup)
- DeckListView mit forest-Theme, deck.color-Streifen, Inbox-Banner
- AccountView mit Sign-out
- DashboardView durch DeckListView ersetzt
- 6 Unit-Tests + 1 UI-Test grün

Phasen-Plan: mana/docs/playbooks/CARDS_NATIVE_GREENFIELD.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 00:06:28 +02:00