cards-native/PLAN.md
Till JS 0b2ae167b7 v0.8.0 — Phase β-7 App-Store-Vorbereitung
Feature-komplett für TestFlight. App-Icon-Platzhalter, Siri-Shortcut,
Share-Extension, Release-Checklist mit allen externen Apple-Schritten.

- scripts/make-appicon.swift: CoreGraphics-basierter Generator für
  1024×1024 forest-green PNG mit "C"-Letter und Karten-Stack-Schatten
- Asset-Catalog auf Single-Size-AppIcon-Pattern umgestellt
- StudyCardsIntent + CardsAppShortcuts (App Intents): Siri-
  Shortcut "Karten lernen mit Cards" / "Mit Cards lernen"
- CardsShareExtension Target: ShareViewController (UIKit-Bootstrap +
  SwiftUI-Hosting), ShareEditorView mit Text-Edit
- PendingShare + PendingShareStore shared in App-Group
  group.ev.mana.cards
- DeckListView zeigt PendingShare-Banner; Tap navigiert zu
  PendingShareConsumeView mit Deck-Picker + Front/Back-Felder, Submit
  → POST /cards, danach store.remove
- Info.plist: NSPhotoLibraryUsageDescription für Image-Occlusion-
  Picker, NSUserActivityTypes für Universal-Links
- docs/RELEASE_CHECKLIST.md mit externen Schritten: Apple-Developer-
  Portal, App-IDs, App-Group, AASA, Xcode-Archive, TestFlight-Plan,
  App-Store-Connect-Felder, Compliance-Verifikation
- UI-Test robuster (akzeptiert Login oder Decks/Entdecken als
  Launch-Erfolg, unabhängig vom Simulator-Keychain-State)
- 35 Tests + 1 UI-Test grün, alle drei Targets bauen

App-Store-Submission selbst ist externe Aktion und passiert nicht
durch dieses Repo — Schritte in docs/RELEASE_CHECKLIST.md.

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

248 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Plan — cards-native (SwiftUI Universal)
**Stand: 2026-05-13 — Phasen β-0 bis β-7 abgeschlossen.**
Feature-komplett für TestFlight. Alle 7 Card-Types + Marketplace
+ Keyboard/Daily-Reminder/Widget + Siri-Shortcut + Share-Extension
+ App-Icon-Platzhalter + Release-Checklist. 35 Unit-Tests + 1 UI-Test
grün, alle drei Targets (Haupt-App + Widget + Share) bauen.
Pflicht-Check für β-2: Endurance-Test auf realem Gerät (200+ Karten
mit Flugmodus zwischendurch) steht aus — Aufgabe für Till.
> **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.cards`, 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.cards`
- `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)
**β-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.cards`
- `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.cards`
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`](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