Drei zusammenhängende Blöcke in einem Commit (Files überlappen sich
zwischen den Themen — sauberer Split nicht ohne Friktion möglich):
1. Wordeck-Text-Only-Cleanup
Image-Occlusion + Audio-Front-Code raus. Server ist seit Migration
0004_wordeck_text_only.sql text-only (in Prod waren 0 Karten der
Typen, 0 Media-Files). Native-Code war Build-11-Altlast.
- Gelöscht: MediaCache, MediaEnvironment, RemoteImage,
AudioPlayerButton, MaskEditorView, CardEditorMediaFields,
CardEditorPayload, Media.swift
- CardType-Enum auf 5 Werte: basic / basic-reverse / cloze /
typing / multiple-choice
- media_refs aus Card, CardCreateBody, CardUpdateBody, call-sites
- WordeckAPI.uploadMedia / .fetchMedia / .deleteMedia + Single-File-
makeMultipartBody gestrichen
- MarketplaceCardConverter ohne Media-Cases
- CardRenderer ohne imageOcclusionView / audioFrontView
2. AI-Media-Mode raus
/decks/from-image-Endpoint existiert serverseitig nicht (server
registriert nur /decks/generate für Text-Prompts). Native-Aufrufe
wären 404 — toter Code.
- aiMedia-Case aus DeckEditorView.CreateMode, ModePicker auf
3 Optionen (Leer / KI / CSV)
- AIMediaFormSections, MediaFileRow, mediaPickers, thumbnail,
ingestPhotoItems, handlePDFImport raus
- generateDeckFromMedia + makeFromImageMultipartBody raus
- GenerationMediaFile-Struct + PhotosUI-Import + PlatformImage-
typealias raus
- NSPhotoLibraryUsageDescription aus project.yml entfernt (es gibt
keinen Photo-Library-Zugriff mehr)
- maxMediaFiles/maxImageBytes/maxPDFBytes + inferImageMimeType +
imageExtension aus DeckEditorHelpers raus
3. ζ-1 Offline-Sync
Konzept in docs/OFFLINE_SYNC.md. Server-authoritative-FSRS bleibt —
kein lokales FSRS, nur Snapshot-Modell.
- Neue SwiftData-Models: CachedCard + CachedDueReview, beide mit
userId/deckId-Indizes
- ModelContainer um die zwei Models erweitert (additive Migration,
sollte automatisch laufen — vor TestFlight verifizieren)
- DueReview bekommt programmatischen init(review:card:) für die
Cache-Rekonstruktion
- DeckListStore.refresh() zieht Cards + Due-Reviews pro Deck
parallel in einer TaskGroup; applyToCache in drei Helpers
gesplittet (applyDecks / applyCards / applyDueReviews)
- Karten: Upsert mit Orphan-Cleanup
- Due-Reviews: voll ersetzt pro Refresh (Server-`due`-Zeiten
ändern sich, Merge wäre falsch)
- StudySession.start() fällt bei Netz-Fehler auf
CachedDueReview-Snapshot zurück, setzt isOfflineSession-Flag
- StudySessionView zeigt offline-Banner und am Ende der Session
einen Hinweis „Weitere Karten erst nach Verbindung verfügbar"
- AccountView.wipeLocalCache(): DSGVO-Wipe vor signOut() und nach
deleteAccount → CachedDeck + CachedCard + CachedDueReview +
PendingGrade werden gelöscht
Plus: Keychain-Test in WordeckNativeTests.swift fix — erwartete
"ev.mana.wordeck", muss seit Cross-App-SSO-Commit 19fee75
ManaSharedKeychainGroup nutzen. Auf Konstant-Reference umgestellt,
damit's nicht wieder driftet.
Verifikation:
- xcodebuild iOS-Simulator: BUILD SUCCEEDED
- swiftlint --strict: 0 violations in 68 files
- swiftformat: clean
- 37/37 Tests grün (inkl. fix-Keychain-Test)
- macOS-Build scheitert an pre-existing .topBarTrailing in
StudySessionView (iOS-only API seit 2026-05-13, nicht durch
diesen Commit verursacht)
Pflicht-Verifikation vor TestFlight (in PLAN.md verewigt):
- SwiftData-Migration auf Bestandsbuilder
- Offline-Endurance (50+ Karten Flugmodus)
- Logout-Wipe mit Account-Switch
- Cross-Check Web ↔ Native nach Offline-Grade
Diff: 35 files, +869 / -1622, netto ~−750 LOC.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
316 lines
18 KiB
Markdown
316 lines
18 KiB
Markdown
# Plan — wordeck-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.
|
||
|
||
**Wordeck-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:** Wordeck, App-ID 6769019526, Bundle
|
||
`ev.mana.wordeck`, Team `QP3GLU8PH3`. AASA + /privacy + /help
|
||
live unter `wordeck.com`.
|
||
|
||
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/wordeck-native`
|
||
- `project.yml` mit Bundle-ID `ev.mana.wordeck`, ManaSwiftCore via
|
||
`path: ../mana-swift-core`
|
||
- `AppConfig` als `ManaAppConfig`-Provider:
|
||
- Auth: `https://auth.mana.how`
|
||
- API: `https://api.wordeck.com`
|
||
- Keychain-Service: `ev.mana.wordeck`
|
||
- `WordeckTheme.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` Wordeck-Rebrand (Bundle `ev.mana.cards` → `ev.mana.wordeck`,
|
||
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 → Wordeck, Build 2
|
||
- `v0.8.6` Wordeck-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 + WordeckAPI.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` Wordeck-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.wordeck`
|
||
- `DeckListStore.refresh` ruft `updateWidgetSnapshot()` und
|
||
`WidgetCenter.shared.reloadAllTimelines()` nach jedem Pull
|
||
- `WordeckWidgetExtension`-Target (eigenes app-extension-Bundle):
|
||
`WordeckWidgetBundle` + `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.wordeck`
|
||
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
|
||
- `WordeckAPI`: 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://wordeck.com/d/<slug>` und `cards://d/<slug>` → Explore-Tab
|
||
öffnet `PublicDeckView`
|
||
- `associated-domains: applinks:wordeck.com` im entitlement
|
||
- 5 neue Marketplace-Decoding-Tests (35 Total grün)
|
||
|
||
**Wichtig:** Universal-Links funktionieren erst, wenn AASA-Endpoint
|
||
unter `wordeck.com/.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
|
||
- `WordeckAPI.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
|
||
- `WordeckAPI`: 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`
|
||
- `WordeckAPI.dueReviews(deckId:)`, `WordeckAPI.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
|
||
- `WordeckAPI.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) |
|
||
| **Wordeck-Cleanup** | ✅ 2026-05-18 | Image-Occlusion + Audio-Front-Code raus (Server seit Migration `0004_wordeck_text_only.sql` text-only). Gelöscht: MediaCache, MediaEnvironment, RemoteImage, AudioPlayerButton, MaskEditorView, CardEditorMediaFields, CardEditorPayload, Media.swift. CardType-Enum auf 5 Werte reduziert, `media_refs` aus Card+CardCreateBody+CardUpdateBody+CardCreate-Call-Sites raus, `WordeckAPI.uploadMedia/.fetchMedia/.deleteMedia` raus, `makeMultipartBody` (Single-File) raus. |
|
||
| **AI-Media-raus** | ✅ 2026-05-18 | `/decks/from-image`-Endpoint existiert serverseitig gar nicht — gesamten Native-Code rausgenommen: `aiMedia`-Case + Sub-Sections in `DeckEditorView`, `generateDeckFromMedia` + `makeFromImageMultipartBody`, `GenerationMediaFile`-Struct, `PhotosUI`-Import, `PlatformImage`-typealias, `NSPhotoLibraryUsageDescription` aus `project.yml`. ModePicker auf 3 Optionen (Leer/KI/CSV). Auch Test fix: `WordeckNativeTests` nutzt jetzt `ManaSharedKeychainGroup` statt String-Literal. 37/37 Tests grün. |
|
||
| **ζ-1 (Offline-Sync)** | ✅ 2026-05-18 | `CachedCard` + `CachedDueReview` SwiftData-Models, `DeckListStore.refresh()` zieht Cards+Due-Reviews pro Deck parallel (TaskGroup) und ersetzt den Snapshot atomar. `StudySession.start()` fällt bei Netz-Fehler auf den Cache zurück, setzt `isOfflineSession`-Flag für UX-Banner. `DueReview` bekommt programmatischen `init(review:card:)` für die Rekonstruktion. `ModelContainer` um die zwei Models erweitert (additive Migration, sollte automatisch durchlaufen). DSGVO-Logout-Wipe in `AccountView`: vor jedem `signOut()` und nach `deleteAccount` werden `CachedDeck`+`CachedCard`+`CachedDueReview`+`PendingGrade` aus dem Context gelöscht. iOS-Build grün, swiftlint --strict clean, 37/37 Tests passen. |
|
||
|
||
## Geplant: ζ-2..4
|
||
|
||
Konzept in [`docs/OFFLINE_SYNC.md`](docs/OFFLINE_SYNC.md).
|
||
|
||
| Phase | Inhalt | Aufwand |
|
||
|---|---|---|
|
||
| ζ-2 | Distractor-Pool für MC-Karten (pro MC-Karte 10 Distractors mit-cachen) | 0,5 Tag |
|
||
| ζ-3 | `SettingsView`-Cache-Footprint anzeigen + manueller Cache-Clear | 0,5 Tag |
|
||
| ζ-4 (optional) | `BGAppRefreshTask`, Wi-Fi-Only-Toggle | 0,5 Tag |
|
||
|
||
Server-authoritative-FSRS bleibt — kein lokales FSRS, nur Snapshot.
|
||
|
||
## Pflicht-Verifikation für ζ-1 (Endurance auf realem Gerät)
|
||
|
||
- [ ] **SwiftData-Migration:** alte App von TestFlight installieren, dann
|
||
über Xcode mit ζ-1-Build überschreiben — Cache muss durchlaufen, kein
|
||
Crash. (Additive Schema-Change sollte automatisch gehen, aber unverifiziert.)
|
||
- [ ] **Offline-Study:** 50+ Karten lernen mit Flugmodus, App killen,
|
||
neu öffnen, weiter lernen — alle Grades landen am Server nach Reconnect.
|
||
- [ ] **Logout-Wipe:** Abmelden, anderer Account anmelden — keine Karten/Decks
|
||
des Vorgängers in der DeckListView sichtbar.
|
||
- [ ] **Cross-Check mit Web:** Karte offline gegradet → Web zeigt identischen
|
||
Review-State nach Reload.
|
||
|
||
## 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 `wordeck.com` (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
|