chore: Rebrand auf ev.mana.cardecky

Apple-Developer-Portal-App-ID lautet ev.mana.cardecky (analog zur
Domain cardecky.mana.how). Alle Bundle-IDs, App-Group, Keychain-
Group, OSLog-Subsysteme, URL-Schemes, Widget-Kind, App-Intent-Phrases,
Marketing-Texte und Doku nachgezogen.

Bundle-IDs neu:
- Main: ev.mana.cardecky
- Widget: ev.mana.cardecky.widget
- Share: ev.mana.cardecky.share
- Tests: ev.mana.cardecky.tests / .uitests

App-Group: group.ev.mana.cardecky
Keychain-Access-Group: $(AppIdentifierPrefix)ev.mana.cardecky
OSLog-Subsystem: ev.mana.cardecky

AASA gleichzeitig in cards-Repo angepasst (Commit 21ec535) und
auf mana-server redeployed — Probe liefert appID
"QP3GLU8PH3.ev.mana.cardecky".

Plus: ShareExtension/Resources/Info.plist + entitlements werden
jetzt analog zu Widget-Resources gitignored (sind XcodeGen-generated).

35 Unit-Tests + 1 UI-Test grün, alle drei Targets bauen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-05-13 13:29:04 +02:00
parent 468ef291f5
commit 4dfb32ba25
17 changed files with 203 additions and 94 deletions

2
.gitignore vendored
View file

@ -11,3 +11,5 @@ Sources/Resources/Info.plist
Sources/Resources/CardsNative.entitlements Sources/Resources/CardsNative.entitlements
Widgets/CardsWidget/Resources/Info.plist Widgets/CardsWidget/Resources/Info.plist
Widgets/CardsWidget/Resources/CardsWidgetExtension.entitlements Widgets/CardsWidget/Resources/CardsWidgetExtension.entitlements
ShareExtension/Resources/Info.plist
ShareExtension/Resources/CardsShareExtension.entitlements

View file

@ -18,7 +18,7 @@ Affordances (Widgets, Notifications, Universal-Links, Pencil).
HTTPS/JWT ┌──────────────────┐ HTTPS/JWT ┌──────────────────┐
cards-api ◄───────────── │ cards-native │ SwiftUI cards-api ◄───────────── │ cards-native │ SwiftUI
cardecky-api.mana.how │ (this repo) │ SwiftData (Cache) cardecky-api.mana.how │ (this repo) │ SwiftData (Cache)
│ ev.mana.cards │ WidgetKit (β-6) │ ev.mana.cardecky │ WidgetKit (β-6)
└──────────────────┘ └──────────────────┘
``` ```
@ -53,7 +53,7 @@ Beschlossen. Nicht ohne explizite Diskussion antasten.
Auth-Implementierung ist verboten. Auth-Implementierung ist verboten.
4. **Pure SwiftUI.** Keine externen UI-Libraries. AppKit/UIKit nur 4. **Pure SwiftUI.** Keine externen UI-Libraries. AppKit/UIKit nur
als Bridge wenn zwingend (z.B. `PencilKit` für Image-Occlusion). als Bridge wenn zwingend (z.B. `PencilKit` für Image-Occlusion).
5. **Bundle-ID `ev.mana.cards`.** Reverse-Domain mana-ev.ch. 5. **Bundle-ID `ev.mana.cardecky`.** Reverse-Domain mana-ev.ch.
Universal-Link-Domain: `cardecky.mana.how`. Universal-Link-Domain: `cardecky.mana.how`.
6. **Cards-Domain-Logik bleibt am Server.** SubIndex-Berechnung für 6. **Cards-Domain-Logik bleibt am Server.** SubIndex-Berechnung für
Cloze, Image-Occlusion-Mask-Validation, Content-Hash — alles Cloze, Image-Occlusion-Mask-Validation, Content-Hash — alles
@ -74,7 +74,7 @@ Beschlossen. Nicht ohne explizite Diskussion antasten.
sind **nicht** im Git sind **nicht** im Git
- **SwiftFormat** mit `.swiftformat` (4-space, 120-col, sorted imports) - **SwiftFormat** mit `.swiftformat` (4-space, 120-col, sorted imports)
- **SwiftLint** mit `.swiftlint.yml` - **SwiftLint** mit `.swiftlint.yml`
- **Logging:** App-Subsystem `ev.mana.cards` via - **Logging:** App-Subsystem `ev.mana.cardecky` via
`Sources/Core/Telemetry/Log.swift`. ManaCore loggt parallel unter `Sources/Core/Telemetry/Log.swift`. ManaCore loggt parallel unter
`ev.mana.core` `ev.mana.core`
- **Persistenz:** SwiftData für Deck/Card-Cache (ab β-1), JWT im - **Persistenz:** SwiftData für Deck/Card-Cache (ab β-1), JWT im
@ -117,7 +117,7 @@ cards-native/
│ │ ├── Domain/ (Card-Type-Enums, Rating-Enum — ab β-2) │ │ ├── Domain/ (Card-Type-Enums, Rating-Enum — ab β-2)
│ │ ├── Storage/ (SwiftData-Models — ab β-1) │ │ ├── Storage/ (SwiftData-Models — ab β-1)
│ │ ├── Sync/ (ReviewQueue, MediaCache — ab β-2/β-4) │ │ ├── Sync/ (ReviewQueue, MediaCache — ab β-2/β-4)
│ │ ├── Telemetry/ OSLog (Subsystem ev.mana.cards) │ │ ├── Telemetry/ OSLog (Subsystem ev.mana.cardecky)
│ │ └── Theme/ CardsTheme (forest-Werte) │ │ └── Theme/ CardsTheme (forest-Werte)
│ ├── Widgets/ (WidgetKit-Extension — ab β-6) │ ├── Widgets/ (WidgetKit-Extension — ab β-6)
│ ├── ShareExtension/ (Save-as-Card — ab β-6) │ ├── ShareExtension/ (Save-as-Card — ab β-6)

View file

@ -17,12 +17,12 @@ mit Flugmodus zwischendurch) steht aus — Aufgabe für Till.
✅ **β-0 — Setup (2026-05-12, Tag `v0.1.0`)** ✅ **β-0 — Setup (2026-05-12, Tag `v0.1.0`)**
- Repo-Skelett unter `git.mana.how/till/cards-native` - Repo-Skelett unter `git.mana.how/till/cards-native`
- `project.yml` mit Bundle-ID `ev.mana.cards`, ManaSwiftCore via - `project.yml` mit Bundle-ID `ev.mana.cardecky`, ManaSwiftCore via
`path: ../mana-swift-core` `path: ../mana-swift-core`
- `AppConfig` als `ManaAppConfig`-Provider: - `AppConfig` als `ManaAppConfig`-Provider:
- Auth: `https://auth.mana.how` - Auth: `https://auth.mana.how`
- API: `https://cardecky-api.mana.how` - API: `https://cardecky-api.mana.how`
- Keychain-Service: `ev.mana.cards` - Keychain-Service: `ev.mana.cardecky`
- `CardsTheme.swift` mit forest-Werten (lokal nachgebaut aus - `CardsTheme.swift` mit forest-Werten (lokal nachgebaut aus
`mana/packages/themes/src/variants/forest.css`) `mana/packages/themes/src/variants/forest.css`)
- `LoginView` (Email/PW gegen mana-auth) - `LoginView` (Email/PW gegen mana-auth)
@ -63,7 +63,7 @@ mit Flugmodus zwischendurch) steht aus — Aufgabe für Till.
- `WidgetSnapshot` Codable mit `topDecks` (Top-3 nach dueCount) - `WidgetSnapshot` Codable mit `topDecks` (Top-3 nach dueCount)
und `totalDueCount` und `totalDueCount`
- `WidgetSnapshotStore` schreibt in App-Group-Container - `WidgetSnapshotStore` schreibt in App-Group-Container
`group.ev.mana.cards` `group.ev.mana.cardecky`
- `DeckListStore.refresh` ruft `updateWidgetSnapshot()` und - `DeckListStore.refresh` ruft `updateWidgetSnapshot()` und
`WidgetCenter.shared.reloadAllTimelines()` nach jedem Pull `WidgetCenter.shared.reloadAllTimelines()` nach jedem Pull
- `CardsWidgetExtension`-Target (eigenes app-extension-Bundle): - `CardsWidgetExtension`-Target (eigenes app-extension-Bundle):
@ -73,7 +73,7 @@ mit Flugmodus zwischendurch) steht aus — Aufgabe für Till.
- `DueProvider` als `TimelineProvider`: liest Snapshot, plant - `DueProvider` als `TimelineProvider`: liest Snapshot, plant
Refresh alle 30 min (plus instant-Refresh via Haupt-App) Refresh alle 30 min (plus instant-Refresh via Haupt-App)
- `DueWidgetView` mit Family-Switch, alle 5 Family-Layouts - `DueWidgetView` mit Family-Switch, alle 5 Family-Layouts
- `com.apple.security.application-groups: group.ev.mana.cards` - `com.apple.security.application-groups: group.ev.mana.cardecky`
im Haupt- und Widget-Entitlement im Haupt- und Widget-Entitlement
- `WidgetSnapshot.swift` in beiden Targets via XcodeGen-source-array - `WidgetSnapshot.swift` in beiden Targets via XcodeGen-source-array
(single-source-of-truth) (single-source-of-truth)

View file

@ -17,7 +17,7 @@ Reachability-Check. Vollständiger Phasen-Plan in
``` ```
HTTPS/JWT ┌──────────────────┐ HTTPS/JWT ┌──────────────────┐
cards-api ◄───────────── │ cards-native │ SwiftUI cards-api ◄───────────── │ cards-native │ SwiftUI
cardecky-api.mana.how │ ev.mana.cards │ WidgetKit (β-6) cardecky-api.mana.how │ ev.mana.cardecky │ WidgetKit (β-6)
└──────────────────┘ └──────────────────┘
┌─────────────────────────────────────────┐ ┌─────────────────────────────────────────┐

View file

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>group.ev.mana.cards</string>
</array>
</dict>
</plist>

View file

@ -1,41 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Als Karte speichern</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>NSExtensionActivationRule</key>
<dict>
<key>NSExtensionActivationSupportsText</key>
<true/>
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
<integer>1</integer>
</dict>
</dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.share-services</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).ShareViewController</string>
</dict>
</dict>
</plist>

View file

@ -72,7 +72,7 @@ final class ShareViewController: UIViewController {
private func cancel() { private func cancel() {
dismiss(animated: true) { [weak self] in dismiss(animated: true) { [weak self] in
self?.extensionContext?.cancelRequest(withError: NSError(domain: "ev.mana.cards.share", code: 0)) self?.extensionContext?.cancelRequest(withError: NSError(domain: "ev.mana.cardecky.share", code: 0))
} }
} }
} }

View file

@ -7,7 +7,7 @@ import ManaCore
enum AppConfig { enum AppConfig {
static let manaAppConfig: ManaAppConfig = DefaultManaAppConfig( static let manaAppConfig: ManaAppConfig = DefaultManaAppConfig(
authBaseURL: URL(string: "https://auth.mana.how")!, authBaseURL: URL(string: "https://auth.mana.how")!,
keychainService: "ev.mana.cards", keychainService: "ev.mana.cardecky",
keychainAccessGroup: nil keychainAccessGroup: nil
) )

View file

@ -15,7 +15,7 @@ final class NotificationManager {
} }
private(set) var authorization: AuthorizationStatus = .unknown private(set) var authorization: AuthorizationStatus = .unknown
private let identifier = "ev.mana.cards.dailyReminder" private let identifier = "ev.mana.cardecky.dailyReminder"
private let store = UserDefaults.standard private let store = UserDefaults.standard
/// Persistiert User-Pref. Format: ISO-Stunde:Minute (default 18:00). /// Persistiert User-Pref. Format: ISO-Stunde:Minute (default 18:00).

View file

@ -18,7 +18,7 @@ struct PendingShare: Codable, Identifiable, Hashable, Sendable {
} }
enum PendingShareStore { enum PendingShareStore {
static let appGroupID = "group.ev.mana.cards" static let appGroupID = "group.ev.mana.cardecky"
static let filename = "pending-shares.json" static let filename = "pending-shares.json"
static var url: URL? { static var url: URL? {

View file

@ -22,7 +22,7 @@ struct WidgetSnapshot: Codable, Sendable {
/// Liest und schreibt WidgetSnapshot in den shared App-Group-Container. /// Liest und schreibt WidgetSnapshot in den shared App-Group-Container.
enum WidgetSnapshotStore { enum WidgetSnapshotStore {
/// App-Group-ID muss exakt mit dem Entitlement-Eintrag matchen. /// App-Group-ID muss exakt mit dem Entitlement-Eintrag matchen.
static let appGroupID = "group.ev.mana.cards" static let appGroupID = "group.ev.mana.cardecky"
static let snapshotFilename = "widget-snapshot.json" static let snapshotFilename = "widget-snapshot.json"
static var snapshotURL: URL? { static var snapshotURL: URL? {

View file

@ -1,13 +1,13 @@
import Foundation import Foundation
import OSLog import OSLog
/// App-eigene OSLog-Logger unter Subsystem `ev.mana.cards`. /// App-eigene OSLog-Logger unter Subsystem `ev.mana.cardecky`.
/// ManaCore loggt unter `ev.mana.core` parallel siehe /// ManaCore loggt unter `ev.mana.core` parallel siehe
/// `mana-swift-core/Sources/ManaCore/Telemetry/CoreLog.swift`. /// `mana-swift-core/Sources/ManaCore/Telemetry/CoreLog.swift`.
enum Log { enum Log {
static let app = Logger(subsystem: "ev.mana.cards", category: "app") static let app = Logger(subsystem: "ev.mana.cardecky", category: "app")
static let auth = Logger(subsystem: "ev.mana.cards", category: "auth") static let auth = Logger(subsystem: "ev.mana.cardecky", category: "auth")
static let api = Logger(subsystem: "ev.mana.cards", category: "api") static let api = Logger(subsystem: "ev.mana.cardecky", category: "api")
static let study = Logger(subsystem: "ev.mana.cards", category: "study") static let study = Logger(subsystem: "ev.mana.cardecky", category: "study")
static let sync = Logger(subsystem: "ev.mana.cards", category: "sync") static let sync = Logger(subsystem: "ev.mana.cardecky", category: "sync")
} }

View file

@ -13,8 +13,8 @@ struct AppConfigTests {
#expect(AppConfig.manaAppConfig.authBaseURL.absoluteString == "https://auth.mana.how") #expect(AppConfig.manaAppConfig.authBaseURL.absoluteString == "https://auth.mana.how")
} }
@Test("Keychain-Service ist ev.mana.cards") @Test("Keychain-Service ist ev.mana.cardecky")
func keychainServiceIsAppSpecific() { func keychainServiceIsAppSpecific() {
#expect(AppConfig.manaAppConfig.keychainService == "ev.mana.cards") #expect(AppConfig.manaAppConfig.keychainService == "ev.mana.cardecky")
} }
} }

View file

@ -2,7 +2,7 @@ import SwiftUI
import WidgetKit import WidgetKit
struct CardsDueWidget: Widget { struct CardsDueWidget: Widget {
let kind: String = "ev.mana.cards.due" let kind: String = "ev.mana.cardecky.due"
var body: some WidgetConfiguration { var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: DueProvider()) { entry in StaticConfiguration(kind: kind, provider: DueProvider()) { entry in

155
docs/MARKETING_COPY.md Normal file
View file

@ -0,0 +1,155 @@
# MARKETING_COPY — cards-native
Vorschläge für App-Store-Description (de + en). Zum Eintragen in
App-Store-Connect. **Nicht final** — vor Submission durch dich
gegenlesen, Tonalität an Vereins-Stil schärfen (siehe `mana/docs/BRAND.md`,
`mana/docs/MISSION.md`).
## Name + Subtitle
**App-Name:** `Cards`
**Subtitle (max 30 Zeichen):** `Karteikarten — mana e.V.`
## Keywords (max 100 Zeichen, comma-separated)
```
Karteikarten,Spaced Repetition,Lernen,Vokabeln,Anki,Flashcards,FSRS,mana,Verein,Open Source
```
(95 Zeichen)
## Description — DE (max 4000 Zeichen)
```
Cards ist die Karteikarten-App des Vereins mana e.V. — Spaced
Repetition wie es sein soll: ohne Werbung, ohne Tracking, ohne
Abo-Zwang.
KARTEN, WIE DU SIE BRAUCHST
- Klassische Vorder-/Rückseite, beidseitig, Lückentext (Cloze),
Eintippen, Multiple Choice
- Bild-Verdeckung mit selbst gesetzten Masken — für Anatomie, Karten,
Diagramme
- Audio-Karten für Sprachen und Aussprache
MODERNER LERN-ALGORITHMUS
Cards nutzt FSRS (Free Spaced Repetition Scheduler), den genauesten
offenen Algorithmus für Karteikarten. Karten kommen wieder, wenn du
sie wirklich brauchst — nicht nach willkürlichen Intervallen.
OFFLINE LERNEN
Karten cachen lokal auf deinem Gerät. Im Flugzeug, in der U-Bahn,
ohne WLAN — Cards funktioniert. Deine Bewertungen werden gequeued
und beim nächsten Online-Moment automatisch hochgeladen.
WIDGETS UND NOTIFICATIONS
Zeige dir die heute fälligen Karten direkt auf dem Home-Bildschirm
oder Lock-Screen. Erinnerung zur Lieblings-Lern-Uhrzeit — lokal, ohne
externe Push-Server.
CARDECKY — MARKETPLACE FÜR LERN-DECKS
Stöbere durch öffentliche Decks aus der Cardecky-Community: Geografie,
Sprachen, Wissenschaft. Abonnieren = Karten landen direkt in deiner
Bibliothek, du kannst sie nach Belieben anpassen.
VEREIN, NICHT FIRMA
Cards wird vom Verein mana e.V. (Schweiz, in Gründung) betrieben.
Kein Tracking, kein Werbe-Anbieter, kein Crash-Reporter. Wenn du dem
Verein etwas zurückgeben willst: mana-ev.ch/spende.
NATIV
Geschrieben in SwiftUI, optimiert für iPhone, iPad und Mac. Apple-
Pencil, Magic-Keyboard und Universal-Links sind voll unterstützt.
Teile aus Safari oder Mail direkt eine neue Karte.
DSGVO-EHRLICH
Deine Daten gehören dir. Export jederzeit als JSON, Lösch-Knopf
löscht alles. Wir hosten in Europa, sprechen Deutsch, und antworten
selbst.
```
## Description — EN (max 4000 Zeichen)
```
Cards is the flashcard app from mana e.V. — spaced repetition the
way it should be: no ads, no tracking, no subscription pressure.
THE CARDS YOU NEED
- Classic front/back, two-sided, cloze, type-in, multiple-choice
- Image occlusion with hand-drawn masks — for anatomy, maps, diagrams
- Audio cards for languages and pronunciation
MODERN SPACED-REPETITION
Cards uses FSRS (Free Spaced Repetition Scheduler), the most accurate
open algorithm available. Cards come back when you actually need them.
OFFLINE-FIRST
Decks cache locally on your device. On a plane, in the subway, off
the grid — Cards keeps working. Your reviews queue up and sync on
the next online moment.
WIDGETS AND REMINDERS
See today's due cards on your Home Screen or Lock Screen. Daily
reminder at your preferred time — local, no push servers.
CARDECKY MARKETPLACE
Browse public decks from the Cardecky community: geography, languages,
science. Subscribe = decks land in your library, ready to be edited.
ASSOCIATION, NOT CORPORATION
Cards is operated by mana e.V. (Switzerland, formation in progress).
No tracking, no ad networks, no crash reporters. Want to give back?
mana-ev.ch/donate.
NATIVE
Built in SwiftUI for iPhone, iPad, and Mac. Apple Pencil,
Magic Keyboard, and Universal Links are first-class. Share from
Safari or Mail to create a card.
GDPR-HONEST
Your data, your call. Full JSON export, single-button delete.
Hosted in Europe, German-speaking support, replies from real humans.
```
## Privacy-Declaration (App-Store-Connect)
| Datentyp | Gesammelt | Zweck | Verlinkt mit User | Tracking |
|---|---|---|---|---|
| Email | Ja | App-Functionality (Login) | Ja | Nein |
| User Content (Cards, Decks) | Ja | App-Functionality | Ja | Nein |
| Usage Data (FSRS Reviews) | Ja | App-Functionality | Ja | Nein |
Keine weiteren Kategorien. Insbesondere: keine `Identifiers`,
keine `Diagnostics`, keine `Location`, keine `Contacts`.
## Privacy-Policy- + Support-URLs
Vor Submission setzen — vermutlich:
- Privacy-Policy: `https://cardecky.mana.how/privacy` (existiert? prüfen)
- Support: `https://cardecky.mana.how/help` oder `kontakt@mana-ev.ch`
- Marketing: `https://cardecky.mana.how`
Falls die URLs noch nicht live sind, vor Submission in cards-web
ergänzen (Routes `/privacy`, `/help`).
## Screenshot-Skizzen
Pro Locale (de, en) jeweils 35 Screenshots zeigen:
1. **DeckListView mit fälligen Karten** — Hauptbildschirm, "X Karten fällig" sichtbar
2. **Study mit gefliptem Cloze** — Markierte Antwort, RatingBar unten
3. **Image-Occlusion** — Bild mit aktiver Maske + Reveal-Hover
4. **Marketplace Explore** — Featured-Carousel
5. **Widget auf Home-Screen** (optional, via Simulator-Screenshot komponiert)
iPhone-Größen (mindestens):
- 6.7" (iPhone 16 Pro Max)
- 6.5" (iPhone XS Max / 11 Pro Max / 14 Plus)
- 5.5" (iPhone 8 Plus — wenn ältere Support-Tier)
Plus iPad:
- 12.9" (iPad Pro)
Apple skaliert die größere Auflösung auf kleinere Slots, wenn keine
spezifischen Screenshots vorhanden — Mindest-Set ist daher Größe 6.7"
für iPhone + 12.9" für iPad. Reicht zur Submission.

View file

@ -11,14 +11,14 @@ AASA) und über Xcode (für Build + Sign).
- [x] **Team-ID gesetzt** (`QP3GLU8PH3`, mana e.V.) — `DEVELOPMENT_TEAM` - [x] **Team-ID gesetzt** (`QP3GLU8PH3`, mana e.V.) — `DEVELOPMENT_TEAM`
in `project.yml > settings > base`. Greift bei Archive automatisch. in `project.yml > settings > base`. Greift bei Archive automatisch.
- [ ] **App-ID `ev.mana.cards`** im Developer-Portal anlegen, falls - [ ] **App-ID `ev.mana.cardecky`** im Developer-Portal anlegen, falls
noch nicht da. Mit Capabilities: App Groups, Keychain Sharing, noch nicht da. Mit Capabilities: App Groups, Keychain Sharing,
Associated Domains. Associated Domains.
- [ ] **App-ID `ev.mana.cards.share`** + **`ev.mana.cards.widget`** für - [ ] **App-ID `ev.mana.cardecky.share`** + **`ev.mana.cardecky.widget`** für
die Extensions analog anlegen, ebenfalls mit App Groups. die Extensions analog anlegen, ebenfalls mit App Groups.
- [ ] **App-Group `group.ev.mana.cards`** im Portal anlegen und allen - [ ] **App-Group `group.ev.mana.cardecky`** im Portal anlegen und allen
drei App-IDs zuweisen. drei App-IDs zuweisen.
- [ ] **Keychain-Access-Group**: heute `ev.mana.cards`. Wenn - [ ] **Keychain-Access-Group**: heute `ev.mana.cardecky`. Wenn
Shared-Keychain mit `memoro-native` gewünscht (siehe Shared-Keychain mit `memoro-native` gewünscht (siehe
`mana/docs/MANA_SWIFT.md` Phase γ), auf `mana/docs/MANA_SWIFT.md` Phase γ), auf
`$(AppIdentifierPrefix)ev.mana.shared` umstellen und `$(AppIdentifierPrefix)ev.mana.shared` umstellen und
@ -49,9 +49,11 @@ AASA) und über Xcode (für Build + Sign).
`cards/infrastructure/docker-compose.production.yml` hinterlegt `cards/infrastructure/docker-compose.production.yml` hinterlegt
(Commit folgt). Wird zur Runtime von `$env/dynamic/public` (Commit folgt). Wird zur Runtime von `$env/dynamic/public`
aufgelöst und in den AASA-Response geschrieben. aufgelöst und in den AASA-Response geschrieben.
- [ ] **Production-Deploy von cards-web** mit dem neuen Compose-Stand: - [x] **Production-Deploy von cards-web** durchgeführt 2026-05-13.
`cd ~/projects/cards/infrastructure && docker compose -f docker-compose.production.yml up -d cards-web` auf mana-server. Probe von außen: `curl https://cardecky.mana.how/.well-known/apple-app-site-association`
Erst danach liefert die AASA die echte Team-ID statt Platzhalter. liefert `application/json` mit `"appID":"QP3GLU8PH3.ev.mana.cardecky"`.
Cloudflare-Tunnel reicht den Endpoint sauber durch (kein
HTML-Captive, kein Redirect).
- [ ] **cardecky-api.mana.how** muss erreichbar bleiben — die App - [ ] **cardecky-api.mana.how** muss erreichbar bleiben — die App
ist 100% Online-write. Health-Probe verifizieren. ist 100% Online-write. Health-Probe verifizieren.
@ -84,12 +86,13 @@ AASA) und über Xcode (für Build + Sign).
### App-Store-Connect ### App-Store-Connect
- [ ] **App-Eintrag erstellen** unter https://appstoreconnect.apple.com - [ ] **App-Eintrag erstellen** unter https://appstoreconnect.apple.com
mit Bundle-ID `ev.mana.cards`. mit Bundle-ID `ev.mana.cardecky`.
- [ ] **App-Name** + **Subtitle** (max 30 Zeichen): - [ ] **App-Name** + **Subtitle** (max 30 Zeichen):
- Name: "Cards" - Name: "Cards"
- Subtitle: "Karteikarten — Verein mana" - Subtitle: "Karteikarten — Verein mana"
- [ ] **Description** (de + en, max 4000 Zeichen). Vorschlag siehe - [ ] **Description** (de + en, max 4000 Zeichen). Vorschlag in
`docs/MARKETING_COPY.md` (existiert noch nicht — TODO). [`docs/MARKETING_COPY.md`](MARKETING_COPY.md) — vor Submission
gegenlesen und Vereins-Tonalität schärfen.
- [ ] **Keywords** (max 100 Zeichen, comma-separated): - [ ] **Keywords** (max 100 Zeichen, comma-separated):
"Karteikarten,Spaced Repetition,Lernen,Vokabeln,Anki,Flashcards,FSRS,mana,Verein,Open Source" "Karteikarten,Spaced Repetition,Lernen,Vokabeln,Anki,Flashcards,FSRS,mana,Verein,Open Source"
- [ ] **Screenshots** für iPhone 16 Pro Max + iPhone SE-3 + iPad Pro. - [ ] **Screenshots** für iPhone 16 Pro Max + iPhone SE-3 + iPad Pro.

View file

@ -60,7 +60,7 @@ targets:
LSApplicationCategoryType: "public.app-category.education" LSApplicationCategoryType: "public.app-category.education"
UILaunchScreen: {} UILaunchScreen: {}
CFBundleURLTypes: CFBundleURLTypes:
- CFBundleURLName: ev.mana.cards - CFBundleURLName: ev.mana.cardecky
CFBundleURLSchemes: CFBundleURLSchemes:
- cards - cards
NSUserActivityTypes: NSUserActivityTypes:
@ -74,14 +74,14 @@ targets:
com.apple.security.network.client: true com.apple.security.network.client: true
com.apple.security.files.user-selected.read-write: true com.apple.security.files.user-selected.read-write: true
keychain-access-groups: keychain-access-groups:
- $(AppIdentifierPrefix)ev.mana.cards - $(AppIdentifierPrefix)ev.mana.cardecky
com.apple.developer.associated-domains: com.apple.developer.associated-domains:
- applinks:cardecky.mana.how - applinks:cardecky.mana.how
com.apple.security.application-groups: com.apple.security.application-groups:
- group.ev.mana.cards - group.ev.mana.cardecky
settings: settings:
base: base:
PRODUCT_BUNDLE_IDENTIFIER: ev.mana.cards PRODUCT_BUNDLE_IDENTIFIER: ev.mana.cardecky
CODE_SIGN_STYLE: Automatic CODE_SIGN_STYLE: Automatic
ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME: AccentColor ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME: AccentColor
@ -111,10 +111,10 @@ targets:
path: ShareExtension/Resources/CardsShareExtension.entitlements path: ShareExtension/Resources/CardsShareExtension.entitlements
properties: properties:
com.apple.security.application-groups: com.apple.security.application-groups:
- group.ev.mana.cards - group.ev.mana.cardecky
settings: settings:
base: base:
PRODUCT_BUNDLE_IDENTIFIER: ev.mana.cards.share PRODUCT_BUNDLE_IDENTIFIER: ev.mana.cardecky.share
CODE_SIGN_STYLE: Automatic CODE_SIGN_STYLE: Automatic
SKIP_INSTALL: "YES" SKIP_INSTALL: "YES"
@ -137,13 +137,13 @@ targets:
path: Widgets/CardsWidget/Resources/CardsWidgetExtension.entitlements path: Widgets/CardsWidget/Resources/CardsWidgetExtension.entitlements
properties: properties:
com.apple.security.application-groups: com.apple.security.application-groups:
- group.ev.mana.cards - group.ev.mana.cardecky
dependencies: dependencies:
- sdk: WidgetKit.framework - sdk: WidgetKit.framework
- sdk: SwiftUI.framework - sdk: SwiftUI.framework
settings: settings:
base: base:
PRODUCT_BUNDLE_IDENTIFIER: ev.mana.cards.widget PRODUCT_BUNDLE_IDENTIFIER: ev.mana.cardecky.widget
CODE_SIGN_STYLE: Automatic CODE_SIGN_STYLE: Automatic
SKIP_INSTALL: "YES" SKIP_INSTALL: "YES"
INFOPLIST_KEY_CFBundleDisplayName: Cards Widget INFOPLIST_KEY_CFBundleDisplayName: Cards Widget
@ -157,7 +157,7 @@ targets:
- target: CardsNative - target: CardsNative
settings: settings:
base: base:
PRODUCT_BUNDLE_IDENTIFIER: ev.mana.cards.tests PRODUCT_BUNDLE_IDENTIFIER: ev.mana.cardecky.tests
GENERATE_INFOPLIST_FILE: "YES" GENERATE_INFOPLIST_FILE: "YES"
CardsNativeUITests: CardsNativeUITests:
@ -169,7 +169,7 @@ targets:
- target: CardsNative - target: CardsNative
settings: settings:
base: base:
PRODUCT_BUNDLE_IDENTIFIER: ev.mana.cards.uitests PRODUCT_BUNDLE_IDENTIFIER: ev.mana.cardecky.uitests
GENERATE_INFOPLIST_FILE: "YES" GENERATE_INFOPLIST_FILE: "YES"
schemes: schemes: