From 813df7c5d26856f3c8f3fcbd5acd05b187ed1e45 Mon Sep 17 00:00:00 2001
From: PianoNic <79938743+Pianonic@users.noreply.github.com>
Date: Wed, 12 Aug 2026 13:39:37 +0200
Subject: [PATCH] Redraw the app-modes diagram around the difference
Show the two paths converging on the same school system, with where the login
and the data come to rest on each, instead of listing the client classes, and
move the device-side details into prose.
---
docs/architecture-modes.md | 71 ++++++++++++++------------------------
1 file changed, 26 insertions(+), 45 deletions(-)
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** |