cards-native/PLAN.md
Till JS 0e90f4b1c1 chore: PLAN.md auf v0.9.4-Stand + Localizable.xcstrings
PLAN.md hatte noch Tag v0.8.0 als letzten Eintrag. Jetzt
Post-β-7-Polish-Sektion mit der vollen Reihe v0.8.1 → v0.9.4 +
Cards-Repo-Hinweis auf 0002_decks_archived_at.

Localizable.xcstrings hat Xcode bei den letzten Builds automatisch
um neue Source-Strings ergänzt (Multiple-Choice, Typing,
CardListSection, etc.) — alle Keys ohne Translations, EN-Fill
ist eine spätere Polish-Aufgabe.

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

15 KiB
Raw Blame History

Plan — cards-native (SwiftUI Universal)

Stand: 2026-05-13 — TestFlight Build 11 (v0.9.4). Alle Phasen β-0 bis β-7 + Polish-Iterationen. 43 Unit-Tests + 1 UI-Test grün.

Cardecky-Web-Look übernommen (v0.9.0 ff.): Fan-Stack-Tiles (5:7 Aspect, 3 rotierte Background-Layer), CardSurface in md/lg/hero, RatingBar mit Good-Emphasis. Tap auf Tile = Study- Mode, Pencil-Icon unten rechts = Deck-Detail. Identische Tile- Optik in Decks- und Entdecken-Tab.

App-Store-Connect: Cardecky, App-ID 6769019526, Bundle ev.mana.cardecky, Team QP3GLU8PH3. AASA + /privacy + /help live unter cardecky.mana.how.

Pflicht-Check für β-2: Endurance-Test auf realem Gerät (200+ Karten mit Flugmodus zwischendurch) steht aus.

SOT: ../mana/docs/playbooks/CARDS_NATIVE_GREENFIELD.md. Dieses File ist die App-lokale Status-Spur, das Greenfield-Doc hat die ganze Architektur-Begründung.

Aktueller Stand

β-0 — Setup (2026-05-12, Tag v0.1.0)

  • Repo-Skelett unter git.mana.how/till/cards-native
  • project.yml mit Bundle-ID ev.mana.cardecky, ManaSwiftCore via path: ../mana-swift-core
  • AppConfig als ManaAppConfig-Provider:
    • Auth: https://auth.mana.how
    • API: https://cardecky-api.mana.how
    • Keychain-Service: ev.mana.cardecky
  • CardsTheme.swift mit forest-Werten (lokal nachgebaut aus mana/packages/themes/src/variants/forest.css)
  • LoginView (Email/PW gegen mana-auth)
  • 3 Unit-Tests (AppConfig)

Post-β-7-Polish (2026-05-13, Tags v0.8.1 → v0.9.4)

Live-Fixes nach Apple-Submission und Real-Device-Tests:

  • v0.8.1 Cardecky-Rebrand (Bundle ev.mana.cardsev.mana.cardecky, AASA + Docker-Compose-Env nachgezogen)
  • v0.8.2 Archive-Polish — Versions-Sync zwischen Targets, iPad- Orientations
  • v0.8.3 Sendable + AppIcon-Asset-Cleanup, /privacy + /help Stubs
  • v0.8.4 PhotosPicker Sendable-Warning via Sub-View-Struct
  • v0.8.5 ITMS-90129-Fix: DisplayName Cards → Cardecky, Build 2
  • v0.8.6 Cardecky-Rebrand User-facing Strings durchgängig
  • v0.8.7 PublicDeckOwner.pseudonym Bool statt String? (Decoder-Crash bei Marketplace-Deck-Open)
  • v0.8.8 Card-Liste in DeckDetailView + CardsAPI.listCards
  • v0.8.9 URL-Query-Bug-Fix in ManaCore.AuthenticatedTransport (URL.appending(path:) encoded ? → 404; gefixt via String-Concat, ManaCore v1.0.1). Behob alle "0-Karten"-Phänomene und das stille Schlucken von Query-Endpoints.
  • v0.9.0 Cardecky-Web-Design: Fan-Stack-Tiles, CardSurface in 3 Sizes, RatingBar mit Good-Emphasis, horizontale Scroll-Sections
  • v0.9.1 Multiple-Choice-Karten gerendert (Distractors via Server, Tap-Selektion, Reveal mit Korrekt/Falsch-Highlight)
  • v0.9.2 Typing-Karten gerendert (Levenshtein-Match 1:1 aus cards-domain portiert: correct/close/wrong, Aliases-Support, Diakritika-Normalisierung)
  • v0.9.3 DEBUG-Auto-Login analog memoro-native (ensureSignedIn() in #if DEBUG) — auch in manaspur-native nachgezogen
  • v0.9.4 Tile-Tap = Study-Mode direkt, Pencil-Edit-Icon unten rechts → DeckDetail, ExploreView mit gleichem Tile-Layout (5:7 Aspect, Kategorie-Icon oben rechts in primary)

Schema-Fix: cards-Repo Commit 4d905bb (0002_decks_archived_at) gleicht Schema-Drift in der Production-DB aus.

β-7 — App-Store-Vorbereitung (2026-05-13, Tag v0.8.0)

  • App-Icon-Platzhalter: scripts/make-appicon.swift generiert 1024×1024 PNG aus CoreGraphics (forest-green + "C"-Letter). Asset-Catalog auf Single-Size-Pattern umgestellt. Vor App-Store-Submit durch Designer- Icon ersetzen (siehe docs/RELEASE_CHECKLIST.md).
  • StudyCardsIntent + CardsAppShortcuts (App Intents Framework): Siri-Shortcut "Karten lernen mit Cards" / "Mit Cards lernen", öffnet die App, App-Shortcut-Provider macht ihn ohne Konfiguration sichtbar.
  • CardsShareExtension-Target (app-extension): empfängt Text/URL aus Safari/Mail-Share-Sheets, SwiftUI-Mini-Editor, persistiert PendingShare in App-Group. Haupt-App zeigt Banner in DeckListView, Tap → PendingShareConsumeView mit Deck-Picker + Front/Back-Felder, Submit → POST /cards, danach PendingShareStore.remove.
  • PendingShare + PendingShareStore shared in beiden Targets.
  • NSPhotoLibraryUsageDescription + NSUserActivityTypes in Info.plist ergänzt für Image-Occlusion-Picker und Universal-Links.
  • docs/RELEASE_CHECKLIST.md — externe Schritte: Apple-Developer- Portal-Konfiguration, AASA-Endpoint, TestFlight-Test-Plan, App-Store- Connect-Felder, Compliance-Verifikation.
  • UI-Test robuster gegen Keychain-State (akzeptiert sowohl Login als auch Decks/Entdecken als gestartete App).

β-6 — Native-Polish (2026-05-13, Tag v0.7.0)

  • Keyboard-Shortcuts in StudySessionView: Space = flip, 1/2/3/4 = again/hard/good/easy (über hidden Buttons mit .keyboardShortcut(.space/KeyEquivalent), iPad-Magic-Keyboard
    • macOS-tauglich)
  • NotificationManager @Observable: Permission-Request, Authorization-State, täglicher UNCalendarNotificationTrigger zur konfigurierten Uhrzeit (UserDefaults-Persistierung)
  • SettingsView (in AccountView verlinkt): Toggle + DatePicker für Reminder, "Über"-Section mit Server-URLs
  • WidgetSnapshot Codable mit topDecks (Top-3 nach dueCount) und totalDueCount
  • WidgetSnapshotStore schreibt in App-Group-Container group.ev.mana.cardecky
  • DeckListStore.refresh ruft updateWidgetSnapshot() und WidgetCenter.shared.reloadAllTimelines() nach jedem Pull
  • CardsWidgetExtension-Target (eigenes app-extension-Bundle): CardsWidgetBundle + CardsDueWidget mit StaticConfiguration, Support für systemSmall, systemMedium, accessoryCircular, accessoryInline, accessoryRectangular
  • DueProvider als TimelineProvider: liest Snapshot, plant Refresh alle 30 min (plus instant-Refresh via Haupt-App)
  • DueWidgetView mit Family-Switch, alle 5 Family-Layouts
  • com.apple.security.application-groups: group.ev.mana.cardecky im Haupt- und Widget-Entitlement
  • WidgetSnapshot.swift in beiden Targets via XcodeGen-source-array (single-source-of-truth)

Deferred auf β-7: Siri-Shortcuts (App Intents), Share-Extension für Save-as-Card. Niedrige Priorität — Keyboard + Notifications + Widget decken 90% des Native-Polish ab.

β-5 — Marketplace (2026-05-13, Tag v0.6.0)

  • PublicDeckEntry, PublicDeck, PublicDeckVersion, PublicDeckOwner, PublicDeckDetail, ExploreResponse, BrowseResponse, SubscribeResponse Codable-DTOs mit snake_case
  • MarketplaceSort Enum (recent/popular/trending) mit deutschen Labels
  • CardsAPI: explore(), browseMarketplace(query:sort:language:), publicDeck(slug:), subscribe(slug:), unsubscribe(slug:)
  • MarketplaceStore @Observable mit Explore-State + Browse-State
  • ExploreView mit Featured + Trending Carousels, Browse-Link
  • BrowseView mit Searchable + Sort-Picker + Liste
  • PublicDeckView mit Header + Version + Owner + Subscribe-Button (Auto-Fork serverseitig, danach NavigationLink zum eigenen Deck)
  • PublicDeckCard + BrowseRow Komponenten mit forest-Theme
  • RootView → TabBar (Decks / Entdecken / Account) statt Single-View
  • Universal-Link-Handler in RootView (onOpenURL + onContinueUserActivity): https://cardecky.mana.how/d/<slug> und cards://d/<slug> → Explore-Tab öffnet PublicDeckView
  • associated-domains: applinks:cardecky.mana.how im entitlement
  • 5 neue Marketplace-Decoding-Tests (35 Total grün)

Wichtig: Universal-Links funktionieren erst, wenn AASA-Endpoint unter cardecky.mana.how/.well-known/apple-app-site-association ausgeliefert wird — heute 404. Web-seitige Aufgabe.

β-4 — Media + Advanced Card-Types (2026-05-13, Tag v0.5.0)

  • MediaUploadResponse DTO + MediaKind-Enum
  • MaskRegion Codable mit 0..1-Coordinates, MaskRegions.parse/encode- Helpers (1:1-Port aus cards-domain/image-occlusion.ts — Sortierung nach ID lexikographisch)
  • CardFieldsBuilder.imageOcclusion, .audioFront mit korrekter mask_regions-Serialisierung als stringified JSON-Array
  • CardsAPI.uploadMedia(data, filename, mimeType) mit Multipart (25 MiB max), .fetchMedia(id) für streamed bytes
  • MediaCache actor mit LRU 200 MB (sortiert nach contentModificationDate)
  • mediaCache-Environment-Key, im App-Entrypoint instantiiert
  • RemoteImage View — authentifiziertes Image-Loading mit ProgressView
    • Failure-State
  • AudioPlayerButton — AVAudioPlayer-Wrapper mit Play/Pause-Toggle, AVAudioSession-Setup für iOS
  • CardRenderer.imageOcclusionView: AsyncImage + opake Maske über aktiver Region (Frontside), Label-Reveal auf Backside
  • CardRenderer.audioFrontView: AudioPlayerButton + back-Text auf Flip
  • MaskEditorView: Touch-Drag-to-Create-Rectangle, Liste mit Label-Edit
    • Delete, 0..1-Normalisierung beim Commit
  • CardEditorView erweitert: PhotosPicker für Image, fileImporter für Audio, Magic-Byte-MIME-Detection (JPEG/PNG/GIF/WebP)
  • 6 neue Tests für MaskRegions-Parse/Encode + Field-Builder (30 Total)

β-3 — Editor (2026-05-13, Tag v0.4.0)

  • DeckCreateBody, DeckUpdateBody, CardCreateBody, CardUpdateBody Encodable-Structs (snake_case via CodingKeys, nil-Felder werden weggelassen)
  • CardFieldsBuilder mit Type-spezifischen Pflicht-Feld-Konstruktoren
  • CardsAPI: createDeck/updateDeck/deleteDeck + createCard/updateCard/deleteCard
  • DeckEditorView für Create + Edit in einer View (mode-switch), Color-Picker mit 8-Preset-Palette aus forest-Theme, Category-Picker (11 Kategorien mit deutschen Labels), Visibility-Segmented-Control
  • CardEditorView mit Type-Picker (basic, basic-reverse, cloze, typing, multiple-choice) und dynamischen Feldern je Typ. Cloze-View zeigt Live-Cluster-Count und Hint-Syntax-Hinweis. image-occlusion und audio-front zeigen β-4-Placeholder
  • DeckDetailView mit 4 Action-Buttons (Lernen, Karte hinzufügen, Bearbeiten, Löschen), Confirmation-Dialog für Delete
  • DeckListView: "+"-Button im Toolbar (Leading), Sheet für Create
  • 7 zusätzliche Encoding-Tests (24 Unit-Tests total)

β-2 — Study-Loop (2026-05-13, Tag v0.3.0)

  • Card, Review, DueReview Codable-DTOs, CardType-Enum (alle 7 Typen)
  • Rating-Enum: again | hard | good | easy mit deutschen Labels
  • Cloze-Helpers (extractClusterIds, subIndexCount, clusterId, renderPrompt, renderAnswer, hint) — 1:1-Port aus cards/packages/cards-domain/src/cloze.ts
  • CardsAPI.dueReviews(deckId:), CardsAPI.gradeReview(...) mit ISO8601-Encoder
  • PendingGrade SwiftData-Model + GradeQueue für Offline-Submit (FIFO-Drain, originaler reviewedAt-Timestamp bleibt erhalten)
  • StudySession als @Observable State-Machine (loading/studying/finished/failed)
  • CardRenderer: basic, basic-reverse (sub-index-abhängig), cloze client-rendered. image-occlusion/audio-front/typing/multiple-choice zeigen Placeholder (β-3/β-4)
  • RatingBar mit Haptic-Feedback (medium für again/hard/good, heavy für easy, soft beim Flip)
  • StudySessionView vollbild aus DeckListView per NavigationLink
  • 9 zusätzliche Tests (Cloze 8x, Review/DueReview-Decoding 3x)

β-1 — Decks lesen (2026-05-13, Tag v0.2.0)

  • Deck-Codable-DTO mit snake_case-CodingKeys, plus DeckCategory, DeckVisibility, FsrsSettings
  • ISO8601-Date-Decoder mit Fractional-Seconds-Toleranz
  • CardsAPI.listDecks(), cardCount(deckId:), dueCount(deckId:)
  • CachedDeck als SwiftData-Model mit lastFetchedAt (Offline-Read)
  • DeckListStore orchestriert API + Cache, paralleles Counts-Fetching via TaskGroup
  • DeckListView mit Pull-to-Refresh, Card/Due-Counts, deck.color-Streifen, Inbox-Banner für Marketplace-Forks
  • AccountView mit Sign-out-Button
  • iOS-Simulator-Build + Tests grün (6 Unit-Tests, 1 UI-Test)

Phasen (Detail in Greenfield-Plan)

Phase Status Inhalt
β-0 2026-05-12 Setup, Login, API-Probe
β-1 2026-05-13 Decks lesen, SwiftData-Cache, Pull-to-Refresh
β-2 2026-05-13 Study-Loop, Offline-Grade-Queue (Endurance-Test offen)
β-3 2026-05-13 Editor: Deck-CRUD + Card-Create (5 Types); Anki-Import auf β-3-ext verschoben
β-4 2026-05-13 Media-Upload, image-occlusion (Touch-Mask-Editor), audio-front (AVAudioPlayer)
β-5 2026-05-13 Marketplace (Explore/Browse/Subscribe) + TabBar + Universal-Link-Handler (AASA server-side pending)
β-6 2026-05-13 Keyboard-Shortcuts + Daily-Reminders + WidgetKit (Siri/Share deferred auf β-7)
β-7 2026-05-13 App-Icon-Platzhalter + Siri-Shortcut + Share-Extension + Release-Checklist (externe Apple-Schritte siehe docs/RELEASE_CHECKLIST.md)

Nächste Schritte: TestFlight + App-Store

Alle remaining steps sind externe Aktionen außerhalb des Repos — Apple-Developer-Portal, App-Store-Connect, Xcode-Archive, das Cards-Web-Repo (AASA). Strukturierte Liste in docs/RELEASE_CHECKLIST.md:

  1. Apple-Developer-Konfiguration (Team-ID, App-IDs, App-Group, Profiles)
  2. App-Icon-Platzhalter durch Designer-Icon ersetzen
  3. AASA-Endpoint auf cardecky.mana.how (Cards-Web-Repo)
  4. Xcode-Archive + TestFlight-Upload
  5. Endurance- und Cross-Device-Tests im TestFlight-Beta
  6. App-Store-Connect-Listing (Description, Screenshots, Privacy)
  7. Submission

Notizen aus β-4

  • PencilKit für Mask-Editor explizit nicht genutzt. Web macht Image-Occlusion-Masks per Touch-Drag-Rechteck (kein Freihand). Server- Schema (MaskRegion = {id, x, y, w, h, label}) erlaubt nur Rechtecke, PencilKit-Strokes wären dafür übersteigert. Wenn später Polygon-Masks oder Freihand-Skizzen dazu kommen, kann PencilKit nachgereicht werden.
  • Apple-Pencil-Support trotzdem grundsätzlich da: SwiftUI's DragGesture reagiert auf Pencil-Eingaben genauso wie auf Finger.

Verschoben auf β-3-Extension oder später

  • Anki-Import (.apkg-Parser): Web parsed client-side und ruft POST /cards pro Karte. Native bräuchte eigenen Swift-Parser für Anki-Pakete (Plist/sqlite/.apkg) — eigener Brocken, nicht blockierend für andere Phasen.
  • Card-Edit (PATCH /cards/:id): Card-Create reicht für Web-Parität in v1, Edit kann später nachgereicht werden.
  • Distractor-Vorschau für Multiple-Choice-Editor: Server liefert Distractors zur Lernzeit (/decks/:deckId/distractors), Editor zeigt sie nicht — Web macht das auch nicht.

Pflicht-Tests für β-2 (vor β-3-Start)

  • Endurance-Test auf realem Gerät: 200+ Karten lernen, Flugmodus zwischendurch — alle Grades landen am Server nach Reconnect.
  • Cross-Check mit Web: Karte gegrade in Native → Web zeigt identischen Review-State nach Reload.

Cross-Refs

  • ../mana/docs/playbooks/CARDS_NATIVE_GREENFIELD.md — Greenfield-Plan SOT
  • ../mana/docs/MANA_SWIFT.md — Plattform-SOT
  • ../cards/CLAUDE.md — Cards-Repo
  • ../cards/STATUS.md — Web-Phasenstand (Referenz)
  • ../mana-swift-core/CLAUDE.md — ManaCore-Konventionen
  • CLAUDE.md — Repo-Konventionen