Files
noo/.claude/context/standards.md
T
ayushyaandClaude Sonnet 5 bb1688ce3a
Build APK / build (push) Successful in 5m17s
Update docs for app lock, multi-account, and local install guidance
Documents the app-lock layer and multi-account storage/session model in
architecture.md/server.md, and adds a standards.md note that `flutter
install` wipes app data (it uninstalls before installing) — use `adb
install -r` for local test deploys instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 16:09:32 -04:00

101 lines
4.7 KiB
Markdown

# Code standards
## Linting
`analysis_options.yaml` includes `package:flutter_lints/flutter.yaml` with
no rules added/relaxed. Run `flutter analyze` before considering a change
done; it should stay clean.
## Comments
Comments are used sparingly and only for non-obvious *why* — a hidden
constraint, a workaround, or the rationale for overriding a framework
default. See `AppTheme._pageTransitionsTheme`/`_sliderTheme`,
`NextcloudService._parseDavDate`/`_davPath`, and `LoginFlowService`'s class
doc comment for the house style: one short doc comment on the
class/function explaining *why* it exists, not what each line does. Don't
add comments that restate the code or describe what a well-named
class/method already makes obvious.
## Widget structure
- Screens (`views/`) are typically `StatefulWidget` when they own
controllers/local UI state (e.g. `LoginView`'s form key + text
controller); presentation is frequently split into small private
`StatelessWidget`s in the same file (`_ServerForm`, `_WaitingForBrowser`
in `login_view.dart`) rather than inlined in one large `build`. Follow
this split for any view complex enough to have more than one visual
"mode".
- Private helpers/widgets are prefixed with `_` and live in the same file as
their one caller; promote to `widgets/` only once something is reused
across files.
- Read provider state with `context.watch<ServerProvider>()` in `build`,
and `context.read<ServerProvider>()` for one-off calls from callbacks
(matches `LoginView._handleContinue`).
## `ServerProvider` conventions
- Any method that fetches data and writes it into a shared field
(`refreshData`, `fetchAllMedia`, `fetchTrash`, `fetchShares`,
`fetchRecent`, `_applyCredentialsForAccount`) must guard against a stale
write from an account the user has since switched away from: capture
`final gen = _sessionGeneration;` at entry, and check
`if (gen != _sessionGeneration) return;` immediately after each `await`
before touching any field or calling `notifyListeners()`. Follow this
pattern for any new fetch method added to the provider.
- New persisted state on `ServerProvider` must be classified global vs.
per-account (see `architecture.md`/`server.md`) up front — global state
uses a plain `_prefsFuture.then((p) => p.setX(key, value))`; per-account
state goes through `_persistAccountPref(key, (p, namespacedKey) =>
p.setX(namespacedKey, value))` and must also be handled in
`_applyAccountPrefs` (both the "reset to default when no account" and the
"load for this account" branches) so it's correct immediately after a
switch, not just at startup.
## Testing
- Widget tests must mock platform channels that the app touches on startup
— `SharedPreferences.setMockInitialValues({})` and a mock
`MethodChannel('plugins.it_nomads.com/flutter_secure_storage')` handler —
and disable Google Fonts network fetching
(`GoogleFonts.config.allowRuntimeFetching = false`) in `setUpAll`. Without
these, `ServerProvider`'s session restore never resolves in the test
sandbox (no plugin implementation is registered, so the read future just
never completes) and the app stays on `_SplashView`'s indeterminate
spinner, which makes `pumpAndSettle()` hang until its own timeout instead
of failing fast. See `test/widget_test.dart` for the reference setup.
- Run with `flutter test`.
## Local install/deploy
Never use `flutter install` to push a build to a test device — it always
does a full **uninstall-then-install** (prints "Uninstalling old
version..."), and Android deletes all app data (SharedPreferences, secure
storage — every saved account/preference) on uninstall. This wipes the app
clean on every single deploy, which looks like an account/settings-loss bug
but is actually just the install method.
Instead, build then install with `adb`'s replace flag, which updates the
APK in place and preserves app data:
```bash
flutter build apk --release
adb install -r build/app/outputs/flutter-apk/app-release.apk
```
This only preserves data if the new APK's signature matches what's already
on the device — a local build's release variant currently reuses the debug
signing config (`android/app/build.gradle.kts`), so consecutive local
builds share a key and `adb install -r` works cleanly. Installing a build
signed with a different key (e.g. a CI-signed release APK from the Gitea
release pipeline) over a differently-signed local build forces Android to
require a full uninstall regardless of the install method used — there's no
way around that from the tooling side.
## Dependencies
Networking is deliberately split: `package:http` for simple JSON/XML
request-response calls, `package:dio` only where streaming/progress is
needed (downloads). Don't introduce a third HTTP client — extend the
existing split instead.