wordeck-native/project.yml
Till JS 9527240bcc feat(offline): text-only Cleanup + ζ-1 Offline-Sync
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>
2026-05-18 22:06:41 +02:00

211 lines
6.4 KiB
YAML

name: WordeckNative
options:
bundleIdPrefix: ev.mana
createIntermediateGroups: true
xcodeVersion: "16.0"
deploymentTarget:
iOS: "18.0"
macOS: "15.0"
developmentLanguage: de
groupSortPosition: top
generateEmptyDirectories: true
packages:
ManaSwiftCore:
path: ../mana-swift-core
ManaSwiftUI:
path: ../mana-swift-ui
settings:
base:
SWIFT_VERSION: "6.0"
SWIFT_STRICT_CONCURRENCY: complete
CURRENT_PROJECT_VERSION: "1"
MARKETING_VERSION: "1.0.0"
GENERATE_INFOPLIST_FILE: "NO"
ENABLE_USER_SCRIPT_SANDBOXING: "YES"
DEAD_CODE_STRIPPING: "YES"
CLANG_ENABLE_MODULES: "YES"
# mana e.V. Apple-Developer-Team-ID. Muss matchen mit dem AASA-
# Eintrag auf https://wordeck.com/.well-known/apple-app-site-association
# (siehe cards/infrastructure/docker-compose.production.yml).
DEVELOPMENT_TEAM: QP3GLU8PH3
targets:
WordeckNative:
type: application
supportedDestinations: [iOS, macOS]
dependencies:
- package: ManaSwiftCore
product: ManaCore
- package: ManaSwiftCore
product: ManaTokens
- package: ManaSwiftUI
product: ManaAuthUI
- target: WordeckWidgetExtension
embed: true
- target: WordeckShareExtension
embed: true
sources:
- path: Sources/App
- path: Sources/Features
- path: Sources/Core
- path: Sources/Resources
excludes:
- "Info.plist"
- "WordeckNative.entitlements"
info:
path: Sources/Resources/Info.plist
properties:
CFBundleShortVersionString: "1.0.0"
CFBundleVersion: "1"
CFBundleDevelopmentRegion: de
CFBundleDisplayName: Wordeck
LSApplicationCategoryType: "public.app-category.education"
UILaunchScreen: {}
UISupportedInterfaceOrientations:
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
UISupportedInterfaceOrientations~ipad:
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationPortraitUpsideDown
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
CFBundleURLTypes:
- CFBundleURLName: ev.mana.wordeck
CFBundleURLSchemes:
- cards
NSUserActivityTypes:
- NSUserActivityTypeBrowsingWeb
ITSAppUsesNonExemptEncryption: false
entitlements:
path: Sources/Resources/WordeckNative.entitlements
properties:
com.apple.security.app-sandbox: true
com.apple.security.network.client: true
com.apple.security.files.user-selected.read-write: true
keychain-access-groups:
- $(AppIdentifierPrefix)ev.mana.session
com.apple.developer.associated-domains:
- applinks:wordeck.com
com.apple.security.application-groups:
- group.ev.mana.wordeck
settings:
base:
PRODUCT_BUNDLE_IDENTIFIER: ev.mana.wordeck
CODE_SIGN_STYLE: Automatic
ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME: AccentColor
ENABLE_PREVIEWS: "YES"
WordeckShareExtension:
type: app-extension
supportedDestinations: [iOS]
sources:
- path: ShareExtension
excludes:
- "Resources/Info.plist"
- "Resources/WordeckShareExtension.entitlements"
- path: Sources/Core/Sync/PendingShareStore.swift
info:
path: ShareExtension/Resources/Info.plist
properties:
CFBundleDisplayName: Als Karte speichern
CFBundleShortVersionString: "1.0.0"
CFBundleVersion: "1"
NSExtension:
NSExtensionPointIdentifier: com.apple.share-services
NSExtensionPrincipalClass: $(PRODUCT_MODULE_NAME).ShareViewController
NSExtensionAttributes:
NSExtensionActivationRule:
NSExtensionActivationSupportsText: true
NSExtensionActivationSupportsWebURLWithMaxCount: 1
entitlements:
path: ShareExtension/Resources/WordeckShareExtension.entitlements
properties:
keychain-access-groups:
- $(AppIdentifierPrefix)ev.mana.session
com.apple.security.application-groups:
- group.ev.mana.wordeck
settings:
base:
PRODUCT_BUNDLE_IDENTIFIER: ev.mana.wordeck.share
CODE_SIGN_STYLE: Automatic
SKIP_INSTALL: "YES"
WordeckWidgetExtension:
type: app-extension
supportedDestinations: [iOS]
sources:
- path: Widgets/WordeckWidget
excludes:
- "Resources/Info.plist"
- "Resources/WordeckWidgetExtension.entitlements"
- path: Sources/Core/Sync/WidgetSnapshot.swift
info:
path: Widgets/WordeckWidget/Resources/Info.plist
properties:
CFBundleDisplayName: Wordeck Widget
CFBundleShortVersionString: "1.0.0"
CFBundleVersion: "1"
NSExtension:
NSExtensionPointIdentifier: com.apple.widgetkit-extension
entitlements:
path: Widgets/WordeckWidget/Resources/WordeckWidgetExtension.entitlements
properties:
keychain-access-groups:
- $(AppIdentifierPrefix)ev.mana.session
com.apple.security.application-groups:
- group.ev.mana.wordeck
dependencies:
- sdk: WidgetKit.framework
- sdk: SwiftUI.framework
settings:
base:
PRODUCT_BUNDLE_IDENTIFIER: ev.mana.wordeck.widget
CODE_SIGN_STYLE: Automatic
SKIP_INSTALL: "YES"
INFOPLIST_KEY_CFBundleDisplayName: Wordeck Widget
WordeckNativeTests:
type: bundle.unit-test
supportedDestinations: [iOS, macOS]
sources:
- Tests/UnitTests
dependencies:
- target: WordeckNative
settings:
base:
PRODUCT_BUNDLE_IDENTIFIER: ev.mana.wordeck.tests
GENERATE_INFOPLIST_FILE: "YES"
WordeckNativeUITests:
type: bundle.ui-testing
supportedDestinations: [iOS, macOS]
sources:
- Tests/UITests
dependencies:
- target: WordeckNative
settings:
base:
PRODUCT_BUNDLE_IDENTIFIER: ev.mana.wordeck.uitests
GENERATE_INFOPLIST_FILE: "YES"
schemes:
WordeckNative:
build:
targets:
WordeckNative: all
WordeckNativeTests: [test]
WordeckNativeUITests: [test]
test:
targets:
- WordeckNativeTests
- WordeckNativeUITests
gatherCoverageData: false
run:
config: Debug
archive:
config: Release