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

289 lines
15 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 — 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.cards``ev.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`](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