Commit graph

8 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
aa94601409 feat(ui): Cardecky-Web-Design — Fan-Stack-Tiles + CardSurface
UI-Refactor angelehnt an cards/apps/web. Drei Killer-Patterns
übernommen:

1. CardSurface (Sources/Core/Theme/CardSurface.swift)
   - Drei Sizes md/lg/hero mit identischem Border-Radius 14pt,
     1pt Border, layered Shadows je nach Elevation
   - Aspect-Ratio 5:7 für md/hero, 12:16.8 für lg
   - Optional Color-Accent-Stripe links (6pt, deck.color)

2. DeckStackTile (Sources/Features/Decks/DeckStackTile.swift)
   - Spielkarten-Stack-Visual: 3 gestaffelt-rotierte
     Hintergrund-Layer hinter der CardSurface
   - Layer-Offsets + Tilts deterministisch aus Deck-ID gehasht
     (gleiches Deck = gleiche Asymmetrie)
   - Inhalt: Category-Icon oben rechts, Titel + Description
     zentriert, Counts unten als Pill für dueCount

3. RatingBar mit Good-Emphasis (Features/Study/RatingBar.swift)
   - "Good" als full primary background (hero action)
   - again/hard/easy mit subtle border-tint + opacity-08-Background
   - Keyboard-Shortcut im Button-Label als kbd-Style-Pill

DeckListView komplett umgebaut:
- Horizontale ScrollView mit scrollTransition + viewAligned-Snap
- Zwei Sektionen: "Eigene Decks" und "Abonniert"
- Inbox-Banner als highlight (primary opacity 0.08 mit border)
- Pending-Share-Banner mit warning-Tint
- Section-Headers mit Icon + Title + Count

StudySessionView.cardSurface nutzt jetzt CardSurface(.hero, .raised).

Build 6 → 7. Drei native Targets bauen, 35 Tests grün.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 17:28:11 +02:00
Till JS
a1770fbc6a v0.7.0 — Phase β-6 Native-Polish
Drei Sub-Pakete: Keyboard-Shortcuts, Daily-Reminder-Notifications,
WidgetKit-Extension mit App-Group-Daten-Sharing. Siri-Shortcuts
und Share-Extension auf β-7 verschoben — niedrige Priorität, die
drei großen Brocken decken 90% des Native-Polish ab.

Keyboard-Shortcuts:
- Hidden Buttons in StudySessionView mit .keyboardShortcut
- Space = flip, 1/2/3/4 = again/hard/good/easy
- iPad-Magic-Keyboard + macOS-tauglich

Daily-Reminders:
- NotificationManager @Observable mit UNUserNotificationCenter
- Authorization-State + Permission-Request-Flow
- UNCalendarNotificationTrigger täglich zur konfigurierten Zeit
- SettingsView in AccountView mit Toggle + DatePicker
- UserDefaults-Persistierung von Hour/Minute/Enabled

WidgetKit-Extension:
- WidgetSnapshot Codable mit topDecks (Top-3 by dueCount) + totalDueCount
- WidgetSnapshotStore schreibt in group.ev.mana.cards-Container
- DeckListStore.refresh schreibt Snapshot + WidgetCenter.reloadAllTimelines
- CardsWidgetExtension-Target im project.yml (app-extension)
- CardsWidgetBundle + CardsDueWidget mit 5 Familien (small/medium/
  accessoryCircular/accessoryInline/accessoryRectangular)
- DueProvider TimelineProvider mit 30-min-Refresh
- DueWidgetView Family-Switch
- WidgetSnapshot.swift shared in beiden Targets via XcodeGen sources
- App-Group im Haupt- und Widget-Entitlement

35 Tests grün (keine neuen Tests in β-6 — WidgetKit + Notifications
sind System-API-Integrationen, Tests wären überwiegend Mocks).
Build inkl. Widget-Extension grün.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 01:00:04 +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
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