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:
parent
468ef291f5
commit
4dfb32ba25
17 changed files with 203 additions and 94 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
8
PLAN.md
8
PLAN.md
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
└──────────────────┘
|
└──────────────────┘
|
||||||
│
|
│
|
||||||
┌─────────────────────────────────────────┐
|
┌─────────────────────────────────────────┐
|
||||||
|
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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).
|
||||||
|
|
|
||||||
|
|
@ -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? {
|
||||||
|
|
|
||||||
|
|
@ -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? {
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
155
docs/MARKETING_COPY.md
Normal 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 3–5 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.
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
20
project.yml
20
project.yml
|
|
@ -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:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue