Commit Graph
2 Commits
Author SHA1 Message Date
JMR-devandClaude Opus 4.8 9a69d175a8 feat(settings): reorder accounts by drag in settings
Give accounts a user-controlled order (issue #164). Every surface that
lists accounts -- the Settings list, the drawer account switcher, and the
compose account picker -- reads the same `ORDER BY sortOrder` query, so a
reorder in Settings is honored app-wide. In Settings a row can be
long-pressed and dragged to a new position; the order persists and
survives restart.

Data layer:
- AccountEntity gains `sortOrder` (@ColumnInfo defaultValue "0"); AccountDao
  orders by it and adds insertAtEnd / reorder / nextSortOrder / setSortOrder,
  the mutations wrapped in transactions.
- New accounts are appended (current max + 1) via insertAtEnd.

Migration (AccountDatabase v1 -> v2):
- ACCOUNT_MIGRATION_1_2 adds the column and backfills existing accounts by
  their previous alphabetical (email) rank, so the already-shown order does
  not shuffle on upgrade. Registered in AccountDatabaseModule; 2.json is
  exported and AccountMigrationTest replays and validates it.
- AccountDataMigrator (the pre-#111 cache->account-db move) creates the
  v2-shaped table and applies the same email-rank backfill, since
  ACCOUNT_MIGRATION_1_2 does not run for that path.

UI:
- AccountReorderList drives long-press drag over a plain Column (no nested
  lazy list inside the scrolling settings column, no extra dependency); the
  pure reorder-index maths (commitDrag) is unit-tested.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 12:22:16 -05:00
JMR-devandClaude Fable 5 9d70bc2932 fix(security): move accounts/credentials to a non-auth-bound database
Accounts, credentials, per-account settings and signatures lived in the
same libremail.db that SQLCipher encrypts under the auth-bound passphrase
when app-lock + encrypted-cache are on. A genuine key invalidation
(biometric re-enrollment or lock removal/re-add) made that file
undecryptable, and the "clear + re-sync" recovery wiped the accounts and
stored credentials along with the mail cache, dropping the user into
onboarding (issue #111).

Move those four tables into a new plaintext AccountDatabase
(libremail-accounts.db) that is never bound to the auth key. Credentials
stay AES-GCM sealed at the column level by the surviving non-auth
KeystoreCrypto master key, so the only secret never touches disk in the
clear. A cache-key invalidation now wipes only libremail.db; the user
stays signed in.

- AccountDatabase (v1) + AccountDatabaseModule; the cache DB drops to v15
  via MIGRATION_14_15. DAOs are unchanged and re-provided from the new DB,
  so no injection site changes.
- AccountDataMigrator performs the one-time cross-DB copy at startup,
  before Room opens either database. It attaches the cache (with its
  resolved passphrase, so an encrypted source is handled) and copies with
  INSERT OR IGNORE. It is crash-safe and idempotent: the source is dropped
  only by MIGRATION_14_15 after the copy, a re-run never duplicates or
  overwrites, and it runs after the clear-pending wipe so an unrecoverable
  cache degrades to "nothing to move" instead of blocking.
- Exported schemas for both databases; MigrationTest asserts the account
  rows/backfills survive to v14 then are dropped at v15, plus a dedicated
  14->15 test. AccountDataMigratorTest covers the plaintext + encrypted
  copy, idempotency, a DDL-vs-Room drift guard, and end-to-end survival of
  a simulated cache wipe.

Closes #111

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 08:25:25 -05:00