Add multi-account support and update project docs

- Add SavedAccount/AccountStore and rework ServerProvider around a list
  of saved accounts: switch/remove/add, swipe-to-cycle on the avatar
  button, per-account browsing prefs (sort/view/filters/cache), and a
  session-generation guard so a stale fetch from an abandoned account
  can't land in the newly active one
- Migrate existing single-account installs into the new schema
  automatically so upgrading users stay logged in
- Settings gains an Accounts section; LoginView gains an "add account"
  mode pushed on top of the main shell instead of replacing it
- Give every Scrollbar in the app a thicker, draggable, Android-style
  thumb via a shared ScrollbarTheme instead of the default hairline
- Splash screen: use the monochrome app icon (tinted per theme) and a
  smaller spinner
- Update CLAUDE.md and .claude/context/*.md to reflect the accumulated
  multi-account, sharing, and UI changes across this session, and add
  a standing instruction to keep these docs current going forward

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-16 11:21:36 -04:00
co-authored by Claude Sonnet 5
parent 5299ac5c2c
commit 1fbdc1f8d0
15 changed files with 1121 additions and 222 deletions
+19
View File
@@ -33,6 +33,25 @@ class/method already makes obvious.
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