diff --git a/docs/architecture-modes.md b/docs/architecture-modes.md
index 31a30cb..ffe3b4c 100644
--- a/docs/architecture-modes.md
+++ b/docs/architecture-modes.md
@@ -5,60 +5,41 @@ systems from the same backend catalog; the difference is **who signs the user in
and **where the data ends up**. No school system is hardcoded in the app - every one
of them comes from the catalog.
-```mermaid
-flowchart TB
- User(["User"])
- User --> Gate{"App mode?"}
-
- Gate -->|"Account"| Login
- Gate -->|"Private (no login)"| Catalog
+Both modes end up reading the same school system. What differs is where your login is
+kept and whether any of the data comes to rest on a server.
- subgraph ACCOUNT["Account mode - Schuly login"]
- direction TB
- Login["OIDC login
(Keycloak)"]
- ApiClient["ApiClient
Bearer token + auto-refresh"]
- Backend[("SchulyBackend
authenticated /api/*")]
- DB[("PostgreSQL
data stored per user")]
- Sync["Plugin sync tasks
(background, recurring)"]
- Login --> ApiClient --> Backend
- Backend <--> DB
- Sync -->|"stores"| DB
- end
-
- subgraph PRIVATE["Private / secure mode - NO login, NO OIDC"]
+```mermaid
+flowchart LR
+ subgraph account["Account mode"]
direction TB
- Catalog["SchoolSystemsService
clean Dio (no auth interceptor)"]
- AnonCat[("GET /api/app/school-systems
[AllowAnonymous]")]
- Connect["Generic connect screen
renders loginFields, branches on privateAuthStrategy"]
- TP["TokenProxyClient
clean Dio"]
- SP["ScrapeProxyClient
clean Dio"]
- Stateless[("Backend stateless proxy
/api/plugins/*/stateless/*
[AllowAnonymous] - stores nothing")]
- Keystore[("On-device keystore only")]
- Auth["Authenticator screen
on-device TOTP from vaulted seed"]
- Catalog --> AnonCat
- Catalog --> Connect
- Connect -->|"token"| TP
- Connect -->|"scrape"| SP
- TP --> Stateless
- SP --> Stateless
- TP -.->|"token + context + email/password/TOTP seed saved"| Keystore
- SP -.->|"creds saved"| Keystore
- Keystore -.->|"seed"| Auth
- Keystore -.->|"silent re-login on expiry"| TP
+ AU(["You"]) -->|"sign in to Schuly"| AB["SchulyBackend"]
+ AB -->|"keeps a copy of your data"| ADB[("Schuly's database")]
end
- subgraph SOURCES["School systems (operator-provided)"]
+ subgraph private["Private mode"]
direction TB
- TokenProvider["Token-strategy provider"]
- ScrapeProvider["Scrape-strategy provider"]
+ PU(["You"]) -->|"sign in to your school"| PP[("Your phone
holds the login")]
+ PP -->|"sent with each request"| PB["SchulyBackend
passes it straight through"]
end
- Sync -->|"proxy"| TokenProvider
- Sync -->|"scrape"| ScrapeProvider
- Stateless -->|"live, nothing stored"| TokenProvider
- Stateless -->|"live, nothing stored"| ScrapeProvider
+ School[("Your school's system")]
+ AB -->|"syncs in the background"| School
+ PB -->|"reads live, stores nothing"| School
```
+## What private mode does on the device
+
+- The school login is written to the **device keystore** and never leaves it, apart from
+ being sent to the school system through the backend's anonymous proxy endpoints.
+- The connect screen is generic: it renders whatever login fields the catalog lists for
+ your school, and follows the `privateAuthStrategy` it declares - `token` (a headless
+ login mints a bearer token and a refreshable session) or `scrape` (the credentials are
+ replayed on each fetch).
+- If your school uses a one-time code, its seed is vaulted with the rest, and the
+ **Authenticator** screen generates codes on the device.
+- When a session expires, the app re-connects silently from the keystore, so you are not
+ asked to sign in again.
+
| | Account mode | Private / secure mode |
| ------------------- | ------------------------------ | ------------------------------------------------- |
| Auth to Schuly | OIDC (Keycloak) bearer | **none** |