Files
certain/ios/README.md
T
GuidoandClaude Opus 5 e2f4e755f1 docs: Uebergabe und README sagen nicht mehr, dass es kein Xcode gibt
Die wichtigste Aussage der Uebergabe war ueberholt: auf dieser Maschine liegt
Xcode 27, Stufe 1 ist gebaut, getestet und im Simulator gestartet. Wer das
nicht weiss, schreibt weiter blind und hinterlaesst dem Nutzer Fehler.

Dazu neu festgehalten: der Befehl nach jeder neuen Datei (xcodegen generate),
was beim Bauen auffiel (nur Swift 5, nicht Swift 6), und der naechste
Stolperstein - App Transport Security laesst nur https zu, auch kein
http://localhost. Ueber die Verbindung geht die Geraetemarke, ein Vollzugang;
die Ausnahme ist deshalb eine Entscheidung des Nutzers, keine Nebensache.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-21 05:47:23 +02:00

140 lines
6.2 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 |
| `Konten/` | Kontenliste (die erste Fachansicht) |
| `Texte/` | Übersetzungen |
| `Tests/` | Reine Logiktests, ohne Netz |
## 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.