Was neu drinsteht, damit der naechste Agent es nicht selbst herausfinden muss: die drei Entwurfsentscheidungen der Stufe 2 (Dashboard in einem Abruf, Nachschlagewerk im AppZustand, Suche erst beim Absenden), die zwei Fallen der Uebertrags-Paarung, und dass die Modellpruefung jetzt ein Test ist statt Handarbeit - samt dem Befehl, die Pruefdatei neu zu erzeugen. Naechster Schritt steht oben: Stufe 3, davor nur noch Backend-Ergaenzung D. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
144 lines
6.5 KiB
Markdown
144 lines
6.5 KiB
Markdown
# Certain für iOS
|
|
|
|
Native App zum Certain-Backend.
|
|
|
|
> **Wer hier weiterarbeitet, liest zuerst [UEBERGABE.md](UEBERGABE.md).** Dort
|
|
> steht, was entschieden ist, was fertig ist, was als Nächstes dran ist und
|
|
> welche Eigenheiten das Backend hat. Dieses README erklärt nur das Projekt.
|
|
|
|
## Bauen und starten
|
|
|
|
Einfach `Certain.xcodeproj` öffnen — es liegt mit im Repository.
|
|
|
|
Die Wahrheit ist aber `project.yml`: eine XcodeGen-Spezifikation, aus der das
|
|
Projekt entsteht. **Wer eine Swift-Datei anlegt, läuft danach einmal
|
|
`xcodegen generate`**, sonst kennt Xcode sie nicht. Ein neuer *Ordner* will
|
|
ausserdem eine Zeile unter `sources:`.
|
|
|
|
```
|
|
brew install xcodegen # einmalig
|
|
cd ios && xcodegen generate
|
|
xcodebuild -project Certain.xcodeproj -scheme Certain \
|
|
-destination 'platform=iOS Simulator,name=iPhone 17 Pro' build
|
|
xcodebuild -project Certain.xcodeproj -scheme Certain \
|
|
-destination 'platform=iOS Simulator,name=iPhone 17 Pro' test
|
|
```
|
|
|
|
Festgelegt sind dort: Bündelkennung `com.scarriffle.certain`, Team
|
|
`PP34X97WS3`, iPhone und iPad, Grundsprache Deutsch, **iOS 26** als
|
|
Mindestversion, Sprachfassung Swift 5. Wer mit einem anderen Apple-Konto baut,
|
|
trägt seine Team-ID in `project.yml` ein.
|
|
|
|
**Nur https.** App Transport Security steht auf der Voreinstellung, ein Server
|
|
unter `http://…` wird also abgewiesen — auch `http://localhost` beim
|
|
Entwickeln. Das ist Absicht: über die Verbindung geht die Gerätemarke, ein
|
|
Vollzugang. Wer eine Ausnahme braucht, trägt sie bewusst in `project.yml` ein.
|
|
|
|
## Was wohin gehört
|
|
|
|
| Ordner | Inhalt |
|
|
|---|---|
|
|
| `App/` | Einstiegspunkt, Navigation, globaler Zustand |
|
|
| `Netzwerk/` | Client, Endpunkte, Fehler, Offline-Ablage |
|
|
| `Modelle/` | Codable-Typen, eins zu eins zu den Backend-Schemas |
|
|
| `Bausteine/` | Wiederverwendetes: Geld, Farben, Symbole, Zustandsansichten |
|
|
| `Anmeldung/` | Server-Adresse, Anmeldung, zweiter Faktor, Passwortwechsel |
|
|
| `Dashboard/` | Die Übersicht: vier feste Karten |
|
|
| `Budget/` | Soll gegen Ist, Monat für Monat |
|
|
| `Buchungen/` | Buchungsliste mit Suche und Filtern |
|
|
| `Abos/` | Verträge und Abos |
|
|
| `Konten/` | Kontenliste |
|
|
| `Texte/` | Übersetzungen, Deutsch und Englisch zeilengleich |
|
|
| `Tests/` | Logiktests ohne Netz, plus die Modellprüfung gegen `Fixtures/antworten.json` |
|
|
|
|
## Wie die Anmeldung funktioniert
|
|
|
|
Das Backend kennt für die Weboberfläche nur ein Sitzungs-Cookie. Für die App
|
|
wurde eine **Gerätemarke der Reichweite `app`** ergänzt
|
|
(`backend/app/models.py`, `TokenScope`). Der Ablauf:
|
|
|
|
1. Nutzer gibt die Server-Adresse ein. Die App prüft mit `GET /api/version`, ob
|
|
dort überhaupt ein Certain läuft.
|
|
2. Nutzer meldet sich mit Benutzername und Passwort an — über das normale
|
|
Cookie, inklusive zweitem Faktor und Pflicht-Passwortwechsel, falls fällig.
|
|
3. Noch in derselben Sitzung legt die App über `POST /api/inbox/tokens` mit
|
|
`scope: "app"` eine Marke an und legt deren Klartext im **Schlüsselbund** ab.
|
|
4. Danach läuft **alles** über den Header `X-Certain-Token`. Das Cookie wird
|
|
verworfen.
|
|
|
|
Damit gibt es kein Passwort auf dem Gerät und keinen Ablauf nach 30 Tagen. Die
|
|
Marke lässt sich am Rechner unter Einstellungen → Gerätemarken einzeln
|
|
widerrufen; danach sperrt sie sofort.
|
|
|
|
Eine App-Marke kann **keine weitere Marke ausstellen** — das geht nur mit einer
|
|
echten Anmeldung. Sonst hätte ein Dieb sich Ersatzschlüssel ausgestellt und das
|
|
Widerrufen wäre wirkungslos.
|
|
|
|
## Später: Erweiterungen
|
|
|
|
Teilen-Menü und Kurzbefehle kommen in Stufe 6. Beide brauchen Zugriff auf
|
|
dieselbe Marke wie die App. Dafür ist im Projekt nötig:
|
|
|
|
- eine **App-Gruppe** (`group.<deine-bundle-id>`) für die Server-Adresse,
|
|
- eine **geteilte Schlüsselbund-Gruppe** für die Marke selbst.
|
|
|
|
`Netzwerk/Ablage.swift` liest beides bereits aus Konstanten, die du an einer
|
|
Stelle setzt (`Ablage.gruppe`). Solange keine Erweiterung existiert, funktioniert
|
|
alles auch ohne App-Gruppe.
|
|
|
|
## Was die App bewusst nicht kann
|
|
|
|
- **Bankkonten verbinden.** Die Bank leitet auf eine feste Web-Adresse zurück,
|
|
die bei Enable Banking registriert ist. Abrufen und Zuordnen geht nativ, das
|
|
Herstellen der Verbindung bleibt im Browser.
|
|
- **Postfach einrichten** und **Sicherung/Wiederherstellung.** Gehört an den
|
|
Rechner.
|
|
- **Benutzerverwaltung.** Administration für Mehrbenutzerbetrieb.
|
|
|
|
## Was geprüft ist — und was nicht
|
|
|
|
**Geprüft:** Jedes Feld der Modelle wurde gegen echte Antworten eines
|
|
laufenden Backends verglichen (`/api/version`, `/api/auth/me`, `/api/accounts`,
|
|
`/api/settings`, `/api/inbox/tokens`). Alle Namen stimmen überein. Dabei kam
|
|
heraus, dass `created_at` ohne Zeitzone und mit Mikrosekunden kommt
|
|
(`2026-10-20T14:18:15.780705`) — dafür hat `Zeitpunkt` einen dritten Leser,
|
|
und `GrundlagenTests` deckt den Fall ab.
|
|
|
|
**Ebenfalls geprüft:** Dass es übersetzt. Ein einziger Fehler in 2600 blind
|
|
geschriebenen Zeilen (`[:]` statt `[]` in `CertainClient.schickeDatei`), danach
|
|
keine Warnung im eigenen Code, 28 Tests grün, Start im Simulator bis zur
|
|
Serverabfrage.
|
|
|
|
**Nicht geprüft:** Der Weg gegen ein echtes Backend — anmelden, zweiter Faktor,
|
|
Marke anlegen, Konten laden.
|
|
|
|
### Wo es beim ersten Bauen am ehesten klemmte
|
|
|
|
Nichts davon trat ein; die Liste bleibt als Landkarte der heiklen Stellen.
|
|
|
|
1. **`@testable import Certain`** in `Tests/GrundlagenTests.swift` — muss auf
|
|
den Namen deines App-Ziels zeigen. Heisst es anders, anpassen.
|
|
2. **`Lader` ist eine generische `@Observable`-Klasse.** Das geht, ist aber
|
|
die ungewöhnlichste Konstruktion hier. Falls der Übersetzer murrt: Die
|
|
Klasse wird bisher nur an einer Stelle benutzt (`KontenListe`).
|
|
3. **Nebenläufigkeit.** `AppZustand` ist `@MainActor`, `CertainClient` ein
|
|
Actor. Unter strikter Nebenläufigkeitsprüfung kann es an den Übergängen
|
|
Warnungen setzen.
|
|
4. **`Tab` und `.tabViewStyle(.sidebarAdaptable)`** in `RootAnsicht` setzen
|
|
iOS 18 voraus. Bei iOS 26 als Mindestversion kein Problem.
|
|
5. **Die Tests nutzen Swift Testing** (`import Testing`, `@Test`, `#expect`),
|
|
nicht XCTest. Xcode 16 und neuer kann beides im selben Ziel.
|
|
|
|
Schick mir die Fehler, dann räumen wir sie weg.
|
|
|
|
## Stand
|
|
|
|
Stufe 1: Gerüst, Anmeldung samt zweitem Faktor und Passwortwechsel,
|
|
Kontenliste. 21 Swift-Dateien, rund 2600 Zeilen. Siehe den Plan im Repository
|
|
für die weiteren Stufen.
|
|
|
|
Noch nicht in der App, obwohl in Stufe 1 erreichbar: Einen zweiten Faktor
|
|
**einrichten** geht nur im Browser. Wer ein Konto hat, für das die Richtlinie
|
|
MFA verlangt, sieht deshalb einen Hinweis mit Link statt eines Einrichtungs-
|
|
Assistenten. Das kommt mit den Einstellungen in Stufe 5.
|