Add CLAUDE.md with topic-specific context docs

Split project context into .claude/context/ (architecture, server,
styling, standards) instead of one large file, referenced from a
minimal root CLAUDE.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-16 00:25:53 -04:00
co-authored by Claude Sonnet 5
parent d68cc43368
commit 0c877e619c
5 changed files with 256 additions and 0 deletions
+63
View File
@@ -0,0 +1,63 @@
# Server integration
## Auth: Login Flow v2
The app **never** collects a Nextcloud password directly. It implements
[Login Flow v2](https://docs.nextcloud.com/server/latest/developer_manual/client_apis/LoginFlow/index.html#login-flow-v2)
via [`LoginFlowService`](../../lib/services/login_flow_service.dart):
1. `LoginFlowService.initiate(serverUrl)` POSTs to
`{server}/index.php/login/v2`, gets back a browser login URL + a poll
endpoint/token.
2. The app opens the login URL in the system browser (`url_launcher`); the
user authenticates and authorizes there.
3. `ServerProvider` polls `LoginFlowService.poll(pollEndpoint, token)` every
2 seconds (`Timer.periodic`, see `_pollTimer`/`_pollTimeoutTimer` in
`server_provider.dart`) until it gets a 200 with `server`/`loginName`/
`appPassword`, a non-404 error, or a 10-minute timeout.
4. The returned **app password** (scoped, revocable) is what gets stored and
used for every subsequent request — real user passwords are never in
memory or on disk.
`LoginFlowStatus` (`idle` → `initiating` → `awaitingBrowser` → `error`)
drives `LoginView`'s UI; see `LoginFlowService`'s doc comment for the full
flow rationale before changing it.
## Talking to the server
[`NextcloudService`](../../lib/services/nextcloud_service.dart) is the
client for an authenticated session — constructed with `serverUrl` +
`username` + the app password, one instance per login (held as
`ServerProvider.service`, recreated on login/logout).
- **Files**: WebDAV (`PROPFIND`/`MKCOL`/`DELETE`/`MOVE` etc. against
`/remote.php/dav/files/{username}/...`) via raw `http`/`dio` calls with a
hand-rolled XML request body and `package:xml` for parsing responses —
there is no WebDAV client dependency. `_parseDavDate`/`_davPath` in this
file exist because WebDAV responses use RFC 1123 dates and either bare
paths or full URLs for `href`; reuse them rather than re-deriving.
- **Everything else** (shares, activity, trash, favorites, quota, user info)
goes through Nextcloud's OCS APIs (`/ocs/v2.php/...`), JSON in, with the
`OCS-APIRequest: true` header required on every OCS call.
- Auth header is HTTP Basic (`username:appPassword`, base64), built in
`_headers`/exposed as `authHeaders` for widgets that need to hit URLs
directly (e.g. `Image.network(url, headers: service.authHeaders)` for
thumbnails/previews).
- Downloads stream through `Dio` (`downloadToFile`) for progress callbacks;
small in-app previews (text/PDF) use `fetchBytes` via `package:http`.
## Session persistence
- **Credentials** (`server`, `loginName`, `appPassword`) live in
`flutter_secure_storage` — OS keychain/keystore-backed, never
`shared_preferences`.
- **UI/app preferences** (theme mode, seed color, dynamic-color toggle,
bottom-bar opacity/blur, grid vs. list, sort field, hidden-files toggle,
etc.) live in `shared_preferences` — see the `_pref*` key constants at the
top of `server_provider.dart`.
- On startup, `ServerProvider._restoreSession()` reads the secure-storage
keys and, if all three are present, rebuilds a `NextcloudService` without
re-hitting the login flow (`_applyCredentials(..., persist: false)`).
`isRestoringSession` gates the splash screen until this resolves — see
`standards.md` for why widget tests must mock both storage channels
rather than relying on this async path throwing naturally.